Merge pull request #57 from l3uddz/develop

Develop
pull/61/head
desimaniac 6 years ago committed by GitHub
commit 4c12365b16
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -22,6 +22,10 @@ __pycache__/
# configs # configs
*.cfg *.cfg
*.json *.json
*.json.sample
# documents
*.md
# generators # generators
*.bat *.bat

@ -1,7 +1,8 @@
[![made-with-python](https://img.shields.io/badge/Made%20with-Python-blue.svg)](https://www.python.org/) w[![made-with-python](https://img.shields.io/badge/Made%20with-Python-blue.svg)](https://www.python.org/)
[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://github.com/l3uddz/traktarr/blob/master/LICENSE) [![License: GPL 3](https://img.shields.io/badge/License-GPL%203-blue.svg)](https://github.com/l3uddz/traktarr/blob/master/LICENSE.md)
[![Discord](https://img.shields.io/discord/381077432285003776.svg?colorB=177DC1&label=Discord)](https://discord.io/cloudbox)
[![Feature Requests](https://img.shields.io/badge/Requests-Feathub-blue.svg)](http://feathub.com/l3uddz/traktarr) [![Feature Requests](https://img.shields.io/badge/Requests-Feathub-blue.svg)](http://feathub.com/l3uddz/traktarr)
[![Discord](https://img.shields.io/discord/381077432285003776.svg)](https://discord.gg/xmNYmSJ)
# traktarr # traktarr
@ -10,7 +11,7 @@
Types of Trakt lists supported: Types of Trakt lists supported:
- Official Trakt lists - Official Trakt Lists
- Trending - Trending
@ -20,13 +21,17 @@ Types of Trakt lists supported:
- Boxoffice - Boxoffice
- Public lists - Most Watched
- Most Played
- Public Lists
- Private lists* - Private Lists*
- Watchlist - Watchlist
- Custom list(s) - Custom List(s)
\* Support for multiple (authenticated) users. \* Support for multiple (authenticated) users.
@ -34,7 +39,6 @@ Types of Trakt lists supported:
<!-- TOC depthFrom:1 depthTo:6 withLinks:1 updateOnSave:1 orderedList:0 --> <!-- TOC depthFrom:1 depthTo:6 withLinks:1 updateOnSave:1 orderedList:0 -->
- [traktarr](#traktarr)
- [Demo](#demo) - [Demo](#demo)
- [Requirements](#requirements) - [Requirements](#requirements)
- [Installation](#installation) - [Installation](#installation)
@ -69,8 +73,9 @@ Types of Trakt lists supported:
- [Movies (Multiple Movies)](#movies-multiple-movies) - [Movies (Multiple Movies)](#movies-multiple-movies)
- [Show (Single Show)](#show-single-show) - [Show (Single Show)](#show-single-show)
- [Shows (Multiple Shows)](#shows-multiple-shows) - [Shows (Multiple Shows)](#shows-multiple-shows)
- [Examples (Manual)](#examples-manual) - [Examples (CLI)](#examples-cli)
- [Movies](#movies)
- [Shows](#shows)
<!-- /TOC --> <!-- /TOC -->
--- ---
@ -213,7 +218,8 @@ You can repeat this process for as many users as you like.
"blacklisted_max_year": 2019, "blacklisted_max_year": 2019,
"blacklisted_min_runtime": 60, "blacklisted_min_runtime": 60,
"blacklisted_min_year": 2000, "blacklisted_min_year": 2000,
"blacklisted_tmdb_ids": [] "blacklisted_tmdb_ids": [],
"rating_limit": ""
}, },
"shows": { "shows": {
"disabled_for": [], "disabled_for": [],
@ -286,6 +292,9 @@ You can repeat this process for as many users as you like.
"trakt": { "trakt": {
"client_id": "", "client_id": "",
"client_secret": "" "client_secret": ""
},
"omdb": {
"api_key": ""
} }
} }
``` ```
@ -299,8 +308,9 @@ You can repeat this process for as many users as you like.
}, },
``` ```
`debug` - show debug messages. `debug` - Toggle debug messages in the log. Default is `false`.
- Default is `false` (keep it off unless your having issues).
- Set to `true`, if you are having issues and want to diagnose why.
## Automatic ## Automatic
@ -308,7 +318,7 @@ Used for automatic / scheduled traktarr tasks.
Movies can be run on a separate schedule then from Shows. Movies can be run on a separate schedule then from Shows.
_Note: These settings are only needed if you plan to use traktarr on a schedule (i.e. via manual/CLI command only); see [Usage](#usage)._ _Note: These settings are only needed if you plan to use traktarr on a schedule (vs just using it as a CLI command only; see [Usage](#usage))._
```json ```json
"automatic": { "automatic": {
@ -318,27 +328,51 @@ _Note: These settings are only needed if you plan to use traktarr on a schedule
"interval": 24, "interval": 24,
"popular": 3, "popular": 3,
"trending": 2, "trending": 2,
"watched": 2,
"played_all": 2,
"watchlist": {}, "watchlist": {},
"lists": {} "lists": {},
}, },
"shows": { "shows": {
"anticipated": 10, "anticipated": 10,
"interval": 48, "interval": 48,
"popular": 1, "popular": 1,
"trending": 2, "trending": 2,
"watched_monthly": 2,
"played": 2,
"watchlist": {}, "watchlist": {},
"lists": {} "lists": {}
} }
}, },
``` ```
`interval` - specify how often (in hours) to run traktarr task. `interval` - Specify how often (in hours) to run traktarr task.
`anticipated`, `popular`, `trending`, `boxoffice` (movies only) - Specify how many items from each Trakt list to find.
`watched` - Adds items that are the most watched by unique Trakt users (mutliple viewings excluded).
`anticipated`, `popular`, `trending`, `boxoffice` (movies only) - specify how many items from each Trakt list to find. - `watched` / `watched_weekly` - Most watched in the week.
`watchlist` - specify which watchlists to fetch (see explanation below) - `watched_monthly` - Most watched in the month.
`lists` - specify which custom lists to fetch (see explanation below) - `watched_yearly` - Most watched in the year.
- `watched_all` - Most watched of all time.
`played` - Adds items that are most the played items by Trakt users (mutliple viewings included).
- `played` / `played_weekly` - Most played in the week.
- `played_monthly` - Most played in the month.
- `played_yearly` - Most played in the year.
- `played_all` Most played of all time.
`watchlist` - Specify which watchlists to fetch (see explanation below).
`lists` - Specify which custom lists to fetch (see explanation below).
### Personal Watchlists ### Personal Watchlists
@ -466,40 +500,54 @@ Use filters to specify the movie/shows's country of origin or blacklist (i.e. fi
"blacklisted_max_year": 2019, "blacklisted_max_year": 2019,
"blacklisted_min_runtime": 60, "blacklisted_min_runtime": 60,
"blacklisted_min_year": 2000, "blacklisted_min_year": 2000,
"blacklisted_tmdb_ids": [] "blacklisted_tmdb_ids": [],
"rating_limit": ""
}, },
``` ```
`disabled_for` - specify for which lists the blacklist must be disabled when running in automatic mode `disabled_for` - Specify for which lists the blacklist is disabled for, when running in automatic mode.
Example: - This is similar to running `--ignore-blacklist` via the CLI command.
``` - Example:
"disabled_for": [
"anticipated",
"watchlist:user1",
"list:http://url-to-list"
],
```
`allowed_countries` - only add movies from these countries. ```
"disabled_for": [
"anticipated",
"watchlist:user1",
"list:http://url-to-list"
],
```
`allowed_languages` - only add movies with these languages (default/blank=English). `allowed_countries` - Only add movies from these countries. Listed as two-letter country codes.
- [List of available country codes](list_of_country_codes.md).
`allowed_languages` - Only add movies with these languages. Listed as two-letter language codes.
- By default, traktarr will only query movies in English. If you need to search for other languages (e.g. Japanese for anime), you must add those languages here.
- By default, traktarr will only query shows in English. If you need to search for other languages (e.g. Japanese for anime), you must add those languages here.
- Languages are in [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format (e.g. `ja` for Japanese.) - Languages are in [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format (e.g. `ja` for Japanese.)
- [List of available language codes](list_of_language_codes.md).
`blacklist_title_keywords` - blacklist certain words in titles. `blacklist_title_keywords` - blacklist certain words in titles.
`blacklisted_genres` - blacklist certain genres. `blacklisted_genres` - Blacklist certain genres.
- [List of available genres](list_of_movie_genres.md).
- For an updated list, visit [here](https://trakt.docs.apiary.io/#reference/genres/list/get-genres).
`blacklisted_max_year` - blacklist release dates after specified year. `blacklisted_max_year` - Blacklist release dates after specified year.
`blacklisted_min_runtime` - blacklist runtime duration lower than specified time (in minutes). `blacklisted_min_runtime` - Blacklist runtime duration lower than specified time (in minutes).
`blacklisted_min_year` - blacklist release dates before specified year. `blacklisted_min_year` - Blacklist release dates before specified year.
`blacklisted_tmdb_ids` - blacklist certain movies with their TMDB IDs. `blacklisted_tmdb_ids` - Blacklist certain movies with their TMDB IDs.
`rating_limit` - Only add movies above this Rotten Tomatoes score.
### Shows ### Shows
@ -546,7 +594,10 @@ Example:
} }
``` ```
`disabled_for` - specify for which lists the blacklist must be disabled when running in automatic mode `disabled_for` - Specify for which lists the blacklist is disabled for, when running in automatic mode.
- This is similar to running `--ignore-blacklist` via the CLI command.
Example: Example:
@ -558,24 +609,33 @@ Example:
], ],
``` ```
`allowed_countries` - only add shows from these countries. `allowed_countries` - Only add shows from these countries. Listed as two-letter country codes.
`allowed_languages` - only add shows with these languages (default/blank=English). - [List of available country codes](list_of_country_codes.md).
`allowed_languages` - Only add shows with these languages.
- By default, traktarr will only query shows in English. If you need to search for other languages (e.g. Japanese for anime), you must add those languages here. - By default, traktarr will only query shows in English. If you need to search for other languages (e.g. Japanese for anime), you must add those languages here.
- Languages are in [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format (e.g. `ja` for Japanese.) - Languages are in [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format (e.g. `ja` for Japanese.)
`blacklisted_genres` - blacklist certain genres. - [List of available language codes](list_of_language_codes.md).
`blacklisted_genres` - Blacklist certain genres.
- [List of available genres](list_of_show_genres.md).
- For an updated list, visit [here](https://trakt.docs.apiary.io/#reference/genres/list/get-genres).
`blacklisted_max_year` - blacklist release dates after specified year. `blacklisted_max_year` - Blacklist release dates after specified year.
`blacklisted_min_runtime` - blacklist runtime duration lower than specified time (in minutes). `blacklisted_min_runtime` - Blacklist runtime duration lower than specified time (in minutes).
`blacklisted_min_year` - blacklist release dates before specified year. `blacklisted_min_year` - Blacklist release dates before specified year.
`blacklisted_networks` - blacklist certain network. `blacklisted_networks` - Blacklist certain network.
`blacklisted_tvdb_ids` - blacklist certain shows with their TVDB IDs. `blacklisted_tvdb_ids` - Blacklist certain shows with their TVDB IDs.
## Notifications ## Notifications
@ -603,13 +663,14 @@ Currently, only Pushover and Slack are supported. More will be added later.
}, },
``` ```
`verbose` - toggle detailed notifications. `verbose` - Toggle detailed notifications. Default is `true`.
- Default is `true` (keep it off unless your having issues).
- Set to `false`, if you want to reduce the amount of detailed notifications (e.g. the names of the movies/shows added vs just the total).
### Pushover ### Pushover
`app_token` and `user_token` - retrieve from Pushover.net. `app_token` and `user_token` - Retrieve from Pushover.net.
You can specify a priority for the messages send via Pushover using the `priority` key. It can be any Pushover priority value (https://pushover.net/api#priority). You can specify a priority for the messages send via Pushover using the `priority` key. It can be any Pushover priority value (https://pushover.net/api#priority).
@ -618,7 +679,7 @@ _Note: The key name (i.e the name right under notifications) can be anything, bu
### Slack ### Slack
`webhook_url` - webhook URL you get after creating an "Incoming Webhook" under "Custom Integrations". `webhook_url` - Webhook URL you get after creating an "Incoming Webhook" under "Custom Integrations".
_Note: The key name (i.e the name right under notifications) can be anything, but the `"service":` must be exactly `"slack"`._ _Note: The key name (i.e the name right under notifications) can be anything, but the `"service":` must be exactly `"slack"`._
@ -665,7 +726,7 @@ Sonarr configuration.
`root_folder` - Root folder for TV shows. `root_folder` - Root folder for TV shows.
`tags` - assign tags to shows based the network it airs on. More details on this below. `tags` - Assign tags to shows based the network it airs on. More details on this below.
`url` - Sonarr's URL. `url` - Sonarr's URL.
@ -726,9 +787,18 @@ Trakt Authentication info:
`client_id` - Fill in your Trakt API key (_Client ID_). `client_id` - Fill in your Trakt API key (_Client ID_).
`client_secret` - Fill in your Trakt Secret key (_Client Scret_) `client_secret` - Fill in your Trakt Secret key (_Client Secret_)
## OMDB
OMDB Authentication info:
```json
"omdb": {
"api_key":""
}
```
`api_key` - Fill in your OMDB API key (*This is only needed if you wish to use rating filtering on adding movies from command line/automatic*)
# Usage # Usage
## Automatic (Scheduled) ## Automatic (Scheduled)
@ -739,7 +809,7 @@ To have traktarr get Movies and Shows for you automatically, on set interval, do
1. `sudo cp /opt/traktarr/systemd/traktarr.service /etc/systemd/system/` 1. `sudo cp /opt/traktarr/systemd/traktarr.service /etc/systemd/system/`
2. `sudo nano /etc/systemd/system/traktarr.service` and edit user/group to match yours. 2. `sudo nano /etc/systemd/system/traktarr.service` and edit user/group to match yours' (run `id` to check).
3. `sudo systemctl daemon-reload` 3. `sudo systemctl daemon-reload`
@ -751,23 +821,56 @@ To have traktarr get Movies and Shows for you automatically, on set interval, do
You can customize how the scheduled traktarr is ran by editing the `traktarr.service` file and adding any of the following options: You can customize how the scheduled traktarr is ran by editing the `traktarr.service` file and adding any of the following options:
\* Remember, other configuration options need to go into the `config.json` file under the `Automatic` section.
``` ```
-d, --add-delay FLOAT Seconds between each add request to Sonarr / Radarr. traktarr run --help
[default: 2.5]
--no-search Disable search when adding to Sonarr / Radarr.
--run-now Do a first run immediately without waiting.
--no-notifications Disable notifications.
--ignore-blacklist Ignores the blacklist when running the command.
--help Show this message and exit.
``` ```
You can bring up the list, anytime, by running the following command: ```
Usage: traktarr run [OPTIONS]
Run in automatic mode.
Options:
-d, --add-delay FLOAT Seconds between each add request to Sonarr /
Radarr. [default: 2.5]
-s, --sort [votes|rating|release]
Sort list to process.
--no-search Disable search when adding to Sonarr /
Radarr.
--run-now Do a first run immediately without waiting.
--no-notifications Disable notifications.
--ignore-blacklist Ignores the blacklist when running the
command.
--help Show this message and exit.
``` ```
traktarr run --help
`-d`, `--add-delay` - Add seconds delay between each add request to Sonarr / Radarr. Dfault is 2.5 seconds.
- Example: `-d 5`
`-s`, `--sort` - Sort list by highest `votes`, highest `rating`, or the latest `release` dates. Default is highest `votes`.
- Example: `-s release`
`--no-search` - Tells Sonarr / Radarr to not automatically search for added shows / movies.
`--run-now` - Traktarr will run first automated search on start, without waiting for next interval.
`--no-notifications` - Disable notifications. Default is `enabled`.
`--ignore-blacklist` - Ignores blacklist filtering. Equivalent of `disabled_for` in config.json.
Example of a modified line from the traktarr.service file that will always add from the most recent releases matched:
```
ExecStart=/usr/bin/python3 /opt/traktarr/traktarr.py run -s release
``` ```
\* Remember, other configuration options need to go into the `config.json` file under the `Automatic` section.
## Manual (CLI) ## Manual (CLI)
@ -830,24 +933,81 @@ Usage: traktarr movies [OPTIONS]
Add multiple movies to Radarr. Add multiple movies to Radarr.
Options: Options:
-t, --list-type TEXT Trakt list to process. For example, anticipated, -t, --list-type TEXT Trakt list to process. For example,
trending, popular, boxoffice, watchlist or any URL anticipated, trending, popular, boxoffice,
to a list [required] watched, played, watchlist or any URL to a
-l, --add-limit INTEGER Limit number of movies added to Radarr. [default: list [required]
0] -l, --add-limit INTEGER Limit number of movies added to Radarr.
-d, --add-delay FLOAT Seconds between each add request to Radarr. [default: 0]
[default: 2.5] -d, --add-delay FLOAT Seconds between each add request to Radarr.
-g, --genre TEXT Only add movies from this genre to Radarr. [default: 2.5]
-f, --folder TEXT Add movies with this root folder to Radarr. -s, --sort [votes|rating|release]
--no-search Disable search when adding movies to Radarr. Sort list to process.
--notifications Send notifications. -r, --rating INTEGER Only add movies above this rating according to Rotten Tomatoese Score
--ignore-blacklist Ignores the blacklist when running the command. [default: 0]
--authenticate-user TEXT Specify which user to authenticate with to -g, --genre TEXT Only add movies from this genre to Radarr.
retrieve Trakt lists. Default: first user in the -f, --folder TEXT Add movies with this root folder to Radarr.
config. --no-search Disable search when adding movies to Radarr.
--help Show this message and exit. --notifications Send notifications.
--authenticate-user TEXT Specify which user to authenticate with to
retrieve Trakt lists. Default: first user in
the config.
--ignore-blacklist Ignores the blacklist when running the
command.
--help Show this message and exit.
``` ```
`-t`, `--list-type` - Trakt list to process. Choices are: `anticipated`, `trending`, `popular`, `boxoffice`, `watched`, `played`, `URL` (trakt list).
- Watched Lists: Movies that are the most watched by unique Trakt users (mutliple viewings excluded).
- `watched` / `watched_weekly` - Most watched in the week.
- `watched_monthly` - Most watched in the month.
- `watched_yearly` - Most watched in the year.
- `watched_all` - Most watched of all time.
- Played Lists: Movies that are the most played by Trakt users (mutliple viewings included).
- `played` / `played_weekly` - Most played in the week.
- `played_monthly` - Most played in the month.
- `played_yearly` - Most played in the year.
- `played_all` Most played of all time.
`-l`, `--add-limit` - Limit number of movies added to Radarr.
`-d`, `--add-delay` - Add seconds delay between each add request to Radarr. Default is 2.5 seconds.
- Example: `-d 5`
`-s`, `--sort` - Sort list by highest `votes`, highest `rating`, or the latest `release` dates. Default is highest `votes`.
- Example: `-s release`
`-r`, `--rating` - Only add movies above this Rotten Tomatoes score.
- Example: `-r 75`
`-g`, `--genre` - Only add movies from this genre to Radarr.
- Can find a list [here](list_of_movie_genres.md).
`-f`, `--folder` - Add shows to a specific root folder in Radarr.
- Example: `-f /mnt/unionfs/Media/Movies/Movies-Kids/`
`--no-search` - Tells Radarr to not automatically search for added movies.
`--notifications` - To enable notifications. Default is `disabled`.
`--ignore-blacklist` - Ignores blacklist filtering. Equivalent of `disabled_for` in config.json.
### Show (Single Show) ### Show (Single Show)
``` ```
@ -883,25 +1043,79 @@ Usage: traktarr shows [OPTIONS]
Add multiple shows to Sonarr. Add multiple shows to Sonarr.
Options: Options:
-t, --list-type TEXT Trakt list to process. For example, anticipated, -t, --list-type TEXT Trakt list to process. For example,
trending, popular, watchlist or any URL to a list anticipated, trending, popular, watched,
[required] played, watchlist or any URL to a list
-l, --add-limit INTEGER Limit number of shows added to Sonarr. [default: [required]
0] -l, --add-limit INTEGER Limit number of shows added to Sonarr.
-d, --add-delay FLOAT Seconds between each add request to Sonarr. [default: 0]
[default: 2.5] -d, --add-delay FLOAT Seconds between each add request to Sonarr.
-g, --genre TEXT Only add shows from this genre to Sonarr. [default: 2.5]
-f, --folder TEXT Add shows with this root folder to Sonarr. -s, --sort [votes|rating|release]
--no-search Disable search when adding shows to Sonarr. Sort list to process.
--notifications Send notifications. -g, --genre TEXT Only add shows from this genre to Sonarr.
--ignore-blacklist Ignores the blacklist when running the command. -f, --folder TEXT Add shows with this root folder to Sonarr.
--authenticate-user TEXT Specify which user to authenticate with to --no-search Disable search when adding shows to Sonarr.
retrieve Trakt lists. Default: first user in the --notifications Send notifications.
config --authenticate-user TEXT Specify which user to authenticate with to
--help Show this message and exit. retrieve Trakt lists. Default: first user in
``` the config
--ignore-blacklist Ignores the blacklist when running the
## Examples (Manual) command.
--help Show this message and exit.
```
`-t`, `--list-type` - Trakt list to process. Choices are: `anticipated`, `trending`, `popular`, `watched`, `played`, `URL` (trakt list).
- Watched Lists: Shows that are the most watched by unique Trakt users (mutliple viewings excluded).
- `watched` / `watched_weekly` - Most watched in the week.
- `watched_monthly` - Most watched in the month.
- `watched_yearly` - Most watched in the year.
- `watched_all` - Most watched of all time.
- Played Lists: Shows that are the most played by Trakt users (mutliple viewings included).
- `played` / `played_weekly` - Most played in the week.
- `played_monthly` - Most played in the month.
- `played_yearly` - Most played in the year.
- `played_all` Most played of all time.
`-l`, `--add-limit` - Limit number of shows added to Sonarr.
`-d`, `--add-delay` - Add seconds delay between each add request to Sonarr. Default is 2.5 seconds.
- Example: `-d 5`
`-s`, `--sort` - Sort list by highest `votes`, highest `rating`, or the latest `release` dates. Default is highest `votes`.
- Example: `-s release`
`-g`, `--genre` - Only add shows from this genre to Sonarr.
- Can find a list [here](list_of_show_genres.md).
`-f`, `--folder` - Add shows to a specific root folder in Sonarr.
- Example: `-f /mnt/unionfs/Media/Shows/Shows-Kids/`
`--no-search` - Tells Sonarr to not automatically search for added shows.
`--notifications` - To enable notifications. Default is `disabled`.
`--ignore-blacklist` - Ignores blacklist filtering. Equivalent of `disabled_for` in config.json.
## Examples (CLI)
### Movies
- Add the movie "Black Panther (2018)": - Add the movie "Black Panther (2018)":
@ -909,43 +1123,102 @@ Options:
traktarr movie -id black-panther-2018 traktarr movie -id black-panther-2018
``` ```
- Add the show "The 100": - Add movies, from the popular list, labeled with the thriller genre, limited to 5 items, and sorted by latest release date.
``` ```
traktarr show -id the-100 traktarr movies -t popular -g thriller -l 5 -s release
``` ```
- Add boxoffice movies, labeled with the comedy genre, limited to 10 items, and send notifications: - Add movies, from the boxoffice list, labeled with the comedy genre, limited to 10 items, and send notifications:
``` ```
traktarr movies -t boxoffice -g comedy -l 10 --notifications traktarr movies -t boxoffice -g comedy -l 10 --notifications
``` ```
- Add popular shows, limited to 2 items, and don't start the search in Sonarr: - Add movies, from a list of most watched played this week, and limited to 5 items.
``` ```
traktarr shows -t popular -l 2 --no-search traktarr movies -t watched -l 5
``` ```
- Add all shows from the watchlist of `user1`: - Add movies, from a list of most played movies this month, and limited to 5 items.
``` ```
traktarr shows -t watchlist --authenticate-user user1 traktarr movies -t played_monthly -l 5
``` ```
- Add all movies from the public list `https://trakt.tv/users/rkerwin/lists/top-100-movies`: - Add (all) movies from the public list `https://trakt.tv/users/rkerwin/lists/top-100-movies`:
``` ```
traktarr movies -t https://trakt.tv/users/rkerwin/lists/top-100-movies traktarr movies -t https://trakt.tv/users/rkerwin/lists/top-100-movies
``` ```
- Add all movies from the private list `https://trakt.tv/users/user1/lists/private-movies-list` of `user1`: - Add (all) movies from the private list `https://trakt.tv/users/user1/lists/private-movies-list` of `user1`:
``` ```
traktarr movies -t https://trakt.tv/users/user1/lists/private-movies-list --authenticate-user=user1 traktarr movies -t https://trakt.tv/users/user1/lists/private-movies-list --authenticate-user=user1
``` ```
## Support on Beerpay - Add movies, from the trending list, with a minimum rating of 80% on Rotten Tomatoes.
Hey dude! Help me out for a couple of :beers:!
```
traktarr movies -t trending -r 80
```
### Shows
- Add the show "The 100":
```
traktarr show -id the-100
```
- Add shows, from the popular list, limited to 5 items, and sorted by higest ratings.
```
traktarr shows -t popular -l 5 -s rating
```
- Add shows, from the popular list, limited to 2 items, and add them but don't search for episodes in Sonarr:
```
traktarr shows -t popular -l 2 --no-search
```
- Add shows, from a list of most watched shows this year, and limited to 5 items.
```
traktarr shows -t watched_yearly -l 5
```
- Add shows, from a list of most played shows this week, and limited to 5 items.
```
traktarr shows -t played -l 5
```
- Add shows, from a list of most played shows of all time, and limited to 5 items.
```
traktarr shows -t played_all -l 5
```
- Add (all) shows from the watchlist of `user1`:
```
traktarr shows -t watchlist --authenticate-user user1
```
***
If you find this project helpful, feel free to make a small donation to the developer:
- [Monzo](https://monzo.me/jamesbayliss9): Credit Cards, Apple Pay, Google Pay
- [Beerpay](https://beerpay.io/l3uddz/traktarr): Credit Cards
[![Beerpay](https://beerpay.io/l3uddz/traktarr/badge.svg?style=beer-square)](https://beerpay.io/l3uddz/traktarr) [![Beerpay](https://beerpay.io/l3uddz/traktarr/make-wish.svg?style=flat-square)](https://beerpay.io/l3uddz/traktarr?focus=wish)
- [Paypal: l3uddz@gmail.com](https://www.paypal.me/l3uddz)
[![Beerpay](https://beerpay.io/l3uddz/traktarr/badge.svg?style=beer-square)](https://beerpay.io/l3uddz/traktarr) [![Beerpay](https://beerpay.io/l3uddz/traktarr/make-wish.svg?style=flat-square)](https://beerpay.io/l3uddz/traktarr?focus=wish) - BTC: 3CiHME1HZQsNNcDL6BArG7PbZLa8zUUgjL

@ -0,0 +1,112 @@
{
"core": {
"debug": false
},
"automatic": {
"movies": {
"anticipated": 3,
"boxoffice": 10,
"interval": 24,
"popular": 3,
"trending": 2
},
"shows": {
"anticipated": 10,
"interval": 48,
"popular": 1,
"trending": 2
}
},
"notifications": {
"verbose": false
},
"filters": {
"shows": {
"allowed_countries": [
"us",
"gb",
"ca"
],
"allowed_languages": [],
"blacklisted_min_runtime": 15,
"blacklisted_min_year": 2010,
"blacklisted_max_year": 2019,
"blacklisted_genres": [
"animation",
"game-show",
"talk-show",
"home-and-garden",
"children",
"reality",
"anime",
"news",
"documentary",
"special-interest"
],
"blacklisted_networks": [
"twitch",
"youtube",
"nickelodeon",
"hallmark",
"reelzchannel",
"disney",
"cnn",
"cbbc",
"the movie network",
"teletoon",
"cartoon network",
"espn",
"fox sports",
"yahoo!"
],
"disabled_for": [],
},
"movies": {
"allowed_countries": [
"us",
"gb",
"ca"
],
"allowed_languages": [],
"blacklisted_genres": [
"documentary",
"music",
"short",
"sporting-event",
"film-noir",
"fan-film"
],
"blacklist_title_keywords": [
"untitled",
"barbie",
"ufc"
],
"blacklisted_min_runtime": 60,
"blacklisted_min_year": 2000,
"blacklisted_max_year": 2019,
"allowed_countries": [
"us",
"gb",
"ca"
],
"disabled_for": [],
}
},
"radarr": {
"api_key": "",
"profile": "HD-1080p",
"url": "http://localhost:7878/",
"root_folder": "/movies/"
},
"sonarr": {
"api_key": "",
"profile": "HD-1080p",
"url": "http://localhost:8989/",
"root_folder": "/tv/",
"tags": {}
},
"trakt": {
"client_id": "",
"client_secret": ""
}
}

@ -17,7 +17,7 @@ def movies_to_tmdb_dict(radarr_movies):
return None return None
def remove_existing_movies(radarr_movies, trakt_movies): def remove_existing_movies(radarr_movies, trakt_movies, callback=None):
new_movies_list = [] new_movies_list = []
if not radarr_movies or not trakt_movies: if not radarr_movies or not trakt_movies:
@ -34,10 +34,14 @@ def remove_existing_movies(radarr_movies, trakt_movies):
for tmp in trakt_movies: for tmp in trakt_movies:
if 'movie' not in tmp or 'ids' not in tmp['movie'] or 'tmdb' not in tmp['movie']['ids']: if 'movie' not in tmp or 'ids' not in tmp['movie'] or 'tmdb' not in tmp['movie']['ids']:
log.debug("Skipping movie because it did not have required fields: %s", tmp) log.debug("Skipping movie because it did not have required fields: %s", tmp)
if callback:
callback('movie', tmp)
continue continue
# check if movie exists in processed_movies # check if movie exists in processed_movies
if tmp['movie']['ids']['tmdb'] in processed_movies: if tmp['movie']['ids']['tmdb'] in processed_movies:
log.debug("Removing existing movie: %s", tmp['movie']['title']) log.debug("Removing existing movie: %s", tmp['movie']['title'])
if callback:
callback('movie', tmp)
continue continue
new_movies_list.append(tmp) new_movies_list.append(tmp)

@ -0,0 +1,30 @@
from misc.log import logger
import json
import requests
log = logger.get_logger(__name__)
def get_rating(apikey,movie):
imdbID = movie['movie']['ids']['imdb']
if(imdbID):
log.debug("Requesting ratings from OMDB for %s (%d) | Genres: %s | Country: %s | imdbID: %s",movie['movie']['title'], movie['movie']['year'],
', '.join(movie['movie']['genres']), movie['movie']['country'].upper(),imdbID)
r = requests.get('http://www.omdbapi.com/?i=' + imdbID + '&apikey=' + apikey)
if(r.status_code == 200):
log.debug("Successfully requested ratings from OMDB for %s (%d) | Genres: %s | Country: %s | imdbID: %s",
movie['movie']['title'], movie['movie']['year'],
', '.join(movie['movie']['genres']), movie['movie']['country'].upper(), imdbID)
for source in json.loads(r.text)["Ratings"]:
if(source['Source'] == 'Rotten Tomatoes'):
log.debug("Rotten Tomatoes shows rating: %s for %s (%d) | Genres: %s | Country: %s | imdbID: %s ",source['Value'],movie['movie']['title'], movie['movie']['year'],
', '.join(movie['movie']['genres']), movie['movie']['country'].upper(),imdbID)
return int(source['Value'].split('%')[0])
else:
log.debug("Error encountered when requesting ratings from OMDB for %s (%d) | Genres: %s | Country: %s | imdbID: %s",
movie['movie']['title'], movie['movie']['year'],
', '.join(movie['movie']['genres']), movie['movie']['country'].upper(), imdbID)
else:
log.debug("Skipping %s (%d) | Genres: %s | Country: %s as it does not have an imdbID",
movie['movie']['title'], movie['movie']['year'],
', '.join(movie['movie']['genres']), movie['movie']['country'].upper())
return -1

@ -48,7 +48,7 @@ def series_to_tvdb_dict(sonarr_series):
return None return None
def remove_existing_series(sonarr_series, trakt_series): def remove_existing_series(sonarr_series, trakt_series, callback=None):
new_series_list = [] new_series_list = []
if not sonarr_series or not trakt_series: if not sonarr_series or not trakt_series:
@ -65,10 +65,14 @@ def remove_existing_series(sonarr_series, trakt_series):
for tmp in trakt_series: for tmp in trakt_series:
if 'show' not in tmp or 'ids' not in tmp['show'] or 'tvdb' not in tmp['show']['ids']: if 'show' not in tmp or 'ids' not in tmp['show'] or 'tvdb' not in tmp['show']['ids']:
log.debug("Skipping show because it did not have required fields: %s", tmp) log.debug("Skipping show because it did not have required fields: %s", tmp)
if callback:
callback('show', tmp)
continue continue
# check if show exists in processed_series # check if show exists in processed_series
if tmp['show']['ids']['tvdb'] in processed_series: if tmp['show']['ids']['tvdb'] in processed_series:
log.debug("Removing existing show: %s", tmp['show']['title']) log.debug("Removing existing show: %s", tmp['show']['title'])
if callback:
callback('show', tmp)
continue continue
new_series_list.append(tmp) new_series_list.append(tmp)

@ -106,7 +106,7 @@ def blacklisted_show_id(show, blacklisted_ids):
return blacklisted return blacklisted
def is_show_blacklisted(show, blacklist_settings, ignore_blacklist): def is_show_blacklisted(show, blacklist_settings, ignore_blacklist, callback=None):
if ignore_blacklist: if ignore_blacklist:
return False return False
@ -125,6 +125,10 @@ def is_show_blacklisted(show, blacklist_settings, ignore_blacklist):
blacklisted = True blacklisted = True
if blacklisted_show_id(show, blacklist_settings.blacklisted_tvdb_ids): if blacklisted_show_id(show, blacklist_settings.blacklisted_tvdb_ids):
blacklisted = True blacklisted = True
if blacklisted and callback:
callback('show', show)
except Exception: except Exception:
log.exception("Exception determining if show was blacklisted %s: ", show) log.exception("Exception determining if show was blacklisted %s: ", show)
return blacklisted return blacklisted
@ -231,7 +235,7 @@ def blacklisted_movie_id(movie, blacklisted_ids):
return blacklisted return blacklisted
def is_movie_blacklisted(movie, blacklist_settings, ignore_blacklist): def is_movie_blacklisted(movie, blacklist_settings, ignore_blacklist, callback=None):
if ignore_blacklist: if ignore_blacklist:
return False return False
@ -250,6 +254,10 @@ def is_movie_blacklisted(movie, blacklist_settings, ignore_blacklist):
blacklisted = True blacklisted = True
if blacklisted_movie_id(movie, blacklist_settings.blacklisted_tmdb_ids): if blacklisted_movie_id(movie, blacklist_settings.blacklisted_tmdb_ids):
blacklisted = True blacklisted = True
if blacklisted and callback:
callback('movie', movie)
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

@ -0,0 +1,79 @@
- `au` (Australia)
- `at` (Austria)
- `be` (Belgium)
- `bo` (Bolivia, Plurinational State of)
- `ba` (Bosnia and Herzegovina)
- `br` (Brazil)
- `io` (British Indian Ocean Territory)
- `bg` (Bulgaria)
- `ca` (Canada)
- `cl` (Chile)
- `cn` (China)
- `co` (Colombia)
- `hr` (Croatia)
- `cu` (Cuba)
- `cz` (Czech Republic)
- `dk` (Denmark)
- `eg` (Egypt)
- `ee` (Estonia)
- `fi` (Finland)
- `fr` (France)
- `pf` (French Polynesia)
- `ge` (Georgia)
- `de` (Germany)
- `gr` (Greece)
- `hk` (Hong Kong)
- `hu` (Hungary)
- `is` (Iceland)
- `in` (India)
- `id` (Indonesia)
- `ir` (Iran, Islamic Republic of)
- `ie` (Ireland)
- `il` (Israel)
- `it` (Italy)
- `jp` (Japan)
- `ke` (Kenya)
- `kp` (Korea, Democratic People's Republic of)
- `kr` (Korea, Republic of)
- `kw` (Kuwait)
- `lv` (Latvia)
- `lb` (Lebanon)
- `lt` (Lithuania)
- `mk` (Macedonia, Republic of)
- `my` (Malaysia)
- `mv` (Maldives)
- `mx` (Mexico)
- `mn` (Mongolia)
- `me` (Montenegro)
- `ma` (Morocco)
- `nl` (Netherlands)
- `nz` (New Zealand)
- `ng` (Nigeria)
- `no` (Norway)
- `pk` (Pakistan)
- `py` (Paraguay)
- `ph` (Philippines)
- `pl` (Poland)
- `pt` (Portugal)
- `ro` (Romania)
- `ru` (Russian Federation)
- `sa` (Saudi Arabia)
- `rs` (Serbia)
- `sg` (Singapore)
- `sk` (Slovakia)
- `si` (Slovenia)
- `za` (South Africa)
- `es` (Spain)
- `sz` (Swaziland)
- `se` (Sweden)
- `ch` (Switzerland)
- `tw` (Taiwan)
- `th` (Thailand)
- `tr` (Turkey)
- `ua` (Ukraine)
- `ae` (United Arab Emirates)
- `gb` (United Kingdom)
- `us` (United States)
- `um` (United States Minor Outlying Islands)
- `ve` (Venezuela, Bolivarian Republic of)
- `vn` (Vietnam)

@ -0,0 +1,131 @@
- `af` (Afrikaans)
- `ak` (Akan)
- `sq` (Albanian)
- `am` (Amharic)
- `ar` (Arabic)
- `hy` (Armenian)
- `as` (Assamese)
- `ay` (Aymara)
- `az` (Azerbaijani)
- `bm` (Bambara)
- `eu` (Basque)
- `be` (Belarusian)
- `bn` (Bengali)
- `nb` (Bokmål, Norwegian; Norwegian Bokmål)
- `bs` (Bosnian)
- `br` (Breton)
- `bg` (Bulgarian)
- `my` (Burmese)
- `ca` (Catalan; Valencian)
- `km` (Central Khmer)
- `ch` (Chamorro)
- `ce` (Chechen)
- `zh` (Chinese)
- `kw` (Cornish)
- `co` (Corsican)
- `cr` (Cree)
- `hr` (Croatian)
- `cs` (Czech)
- `da` (Danish)
- `dv` (Divehi; Dhivehi; Maldivian)
- `nl` (Dutch; Flemish)
- `dz` (Dzongkha)
- `en` (English)
- `eo` (Esperanto)
- `et` (Estonian)
- `ee` (Ewe)
- `fo` (Faroese)
- `fi` (Finnish)
- `fr` (French)
- `gd` (Gaelic; Scottish Gaelic)
- `gl` (Galician)
- `ka` (Georgian)
- `de` (German)
- `el` (Greek, Modern (1453-))
- `gn` (Guarani)
- `gu` (Gujarati)
- `ht` (Haitian; Haitian Creole)
- `ha` (Hausa)
- `he` (Hebrew)
- `hi` (Hindi)
- `hu` (Hungarian)
- `is` (Icelandic)
- `ig` (Igbo)
- `id` (Indonesian)
- `iu` (Inuktitut)
- `ga` (Irish)
- `it` (Italian)
- `ja` (Japanese)
- `jv` (Javanese)
- `kl` (Kalaallisut; Greenlandic)
- `kn` (Kannada)
- `kk` (Kazakh)
- `rw` (Kinyarwanda)
- `ky` (Kirghiz; Kyrgyz)
- `ko` (Korean)
- `ku` (Kurdish)
- `lo` (Lao)
- `la` (Latin)
- `lv` (Latvian)
- `lt` (Lithuanian)
- `lb` (Luxembourgish; Letzeburgesch)
- `mk` (Macedonian)
- `mg` (Malagasy)
- `ms` (Malay)
- `ml` (Malayalam)
- `mt` (Maltese)
- `mi` (Maori)
- `mr` (Marathi)
- `mh` (Marshallese)
- `mn` (Mongolian)
- `nv` (Navajo; Navaho)
- `nr` (Ndebele, South; South Ndebele)
- `ne` (Nepali)
- `no` (Norwegian)
- `nn` (Norwegian Nynorsk; Nynorsk, Norwegian)
- `oc` (Occitan (post 1500); Provençal)
- `pa` (Panjabi; Punjabi)
- `fa` (Persian)
- `pl` (Polish)
- `pt` (Portuguese)
- `ps` (Pushto; Pashto)
- `qu` (Quechua)
- `ro` (Romanian; Moldavian; Moldovan)
- `rn` (Rundi)
- `ru` (Russian)
- `sm` (Samoan)
- `sg` (Sango)
- `sa` (Sanskrit)
- `sr` (Serbian)
- `sn` (Shona)
- `si` (Sinhala; Sinhalese)
- `sk` (Slovak)
- `sl` (Slovenian)
- `so` (Somali)
- `es` (Spanish; Castilian)
- `sw` (Swahili)
- `sv` (Swedish)
- `tl` (Tagalog)
- `ty` (Tahitian)
- `tg` (Tajik)
- `ta` (Tamil)
- `tt` (Tatar)
- `te` (Telugu)
- `th` (Thai)
- `bo` (Tibetan)
- `ti` (Tigrinya)
- `to` (Tonga (Tonga Islands))
- `tr` (Turkish)
- `tk` (Turkmen)
- `uk` (Ukrainian)
- `ur` (Urdu)
- `uz` (Uzbek)
- `ve` (Venda)
- `vi` (Vietnamese)
- `cy` (Welsh)
- `fy` (Western Frisian)
- `wo` (Wolof)
- `xh` (Xhosa)
- `yi` (Yiddish)
- `za` (Zhuang; Chuang)
- `zu` (Zulu)

@ -0,0 +1,34 @@
- `action`
- `adventure`
- `animation`
- `anime`
- `comedy`
- `crime`
- `disaster`
- `documentary`
- `drama`
- `eastern`
- `family`
- `fan-film`
- `fantasy`
- `film-noir`
- `history`
- `holiday`
- `horror`
- `indie`
- `music`
- `musical`
- `mystery`
- `none`
- `road`
- `romance`
- `science-fiction`
- `short`
- `sporting-event`
- `sports`
- `superhero`
- `suspense`
- `thriller`
- `tv-movie`
- `war`
- `western`

@ -0,0 +1,39 @@
- `action`
- `adventure`
- `animation`
- `anime`
- `biography`
- `children`
- `comedy`
- `crime`
- `disaster`
- `documentary`
- `drama`
- `eastern`
- `family`
- `fantasy`
- `game-show`
- `history`
- `holiday`
- `home-and-garden`
- `horror`
- `mini-series`
- `music`
- `musical`
- `mystery`
- `news`
- `none`
- `reality`
- `romance`
- `science-fiction`
- `short`
- `soap`
- `special-interest`
- `sporting-event`
- `sports`
- `superhero`
- `suspense`
- `talk-show`
- `thriller`
- `war`
- `western`

@ -20,14 +20,17 @@ class Trakt:
# Requests # Requests
############################################################ ############################################################
def _make_request(self, url, payload={}, authenticate_user=None): def _make_request(self, url, payload={}, authenticate_user=None, request_type='get'):
headers, authenticate_user = self._headers(authenticate_user) headers, authenticate_user = self._headers(authenticate_user)
if authenticate_user: if authenticate_user:
url = url.replace('{authenticate_user}', authenticate_user) url = url.replace('{authenticate_user}', authenticate_user)
# make request # make request
req = requests.get(url, headers=headers, params=payload, timeout=30) if request_type == 'delete':
req = requests.delete(url, headers=headers, params=payload, timeout=30)
else:
req = requests.get(url, headers=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("Request User: %s", authenticate_user) log.debug("Request User: %s", authenticate_user)
@ -82,13 +85,21 @@ class Trakt:
if req.status_code == 200: if req.status_code == 200:
resp_json = req.json() resp_json = req.json()
if type_name == 'person' and 'cast' in resp_json:
for item in resp_json: # handle person results
if item not in processed: for item in resp_json['cast']:
if object_name.rstrip('s') not in item and 'title' in item: if item not in processed:
processed.append({object_name.rstrip('s'): item}) if object_name.rstrip('s') not in item and 'title' in item:
else: processed.append({object_name.rstrip('s'): item})
processed.append(item) else:
processed.append(item)
else:
for item in resp_json:
if item not in processed:
if object_name.rstrip('s') not in item and 'title' in item:
processed.append({object_name.rstrip('s'): item})
else:
processed.append(item)
# check if we have fetched the last page, break if so # check if we have fetched the last page, break if so
if total_pages == 0: if total_pages == 0:
@ -130,6 +141,16 @@ class Trakt:
log.exception("Exception validating client_id: ") log.exception("Exception validating client_id: ")
return False return False
def remove_recommended_item(self, item_type, trakt_id, authenticate_user=None):
ret = self._make_request(
url='https://api.trakt.tv/recommendations/%ss/%s' % (item_type, str(trakt_id)),
authenticate_user=authenticate_user,
request_type='delete'
)
if ret.status_code == 204:
return True
return False
############################################################ ############################################################
# OAuth Authentication # OAuth Authentication
############################################################ ############################################################
@ -343,6 +364,16 @@ class Trakt:
genres=genres genres=genres
) )
def get_person_shows(self, person, limit=1000, languages=None, genres=None):
return self._make_items_request(
url='https://api.trakt.tv/people/%s/shows' % person,
limit=limit,
languages=languages,
object_name='shows',
type_name='person',
genres=genres
)
def get_most_played_shows(self, limit=1000, languages=None, genres=None, most_type=None): def get_most_played_shows(self, limit=1000, languages=None, genres=None, most_type=None):
return self._make_items_request( return self._make_items_request(
url='https://api.trakt.tv/shows/played/%s' % ('weekly' if not most_type else most_type), url='https://api.trakt.tv/shows/played/%s' % ('weekly' if not most_type else most_type),
@ -363,6 +394,17 @@ class Trakt:
genres=genres genres=genres
) )
def get_recommended_shows(self, authenticate_user=None, limit=1000, languages=None, genres=None):
return self._make_items_request(
url='https://api.trakt.tv/recommendations/shows',
authenticate_user=authenticate_user,
limit=limit,
languages=languages,
object_name='shows',
type_name='recommended from {authenticate_user}',
genres=genres
)
def get_watchlist_shows(self, authenticate_user=None, limit=1000, languages=None): def get_watchlist_shows(self, authenticate_user=None, limit=1000, languages=None):
return self._make_items_request( return self._make_items_request(
url='https://api.trakt.tv/users/{authenticate_user}/watchlist/shows', url='https://api.trakt.tv/users/{authenticate_user}/watchlist/shows',
@ -427,6 +469,16 @@ class Trakt:
genres=genres genres=genres
) )
def get_person_movies(self, person, limit=1000, languages=None, genres=None):
return self._make_items_request(
url='https://api.trakt.tv/people/%s/movies' % person,
limit=limit,
languages=languages,
object_name='movies',
type_name='person',
genres=genres
)
def get_most_played_movies(self, limit=1000, languages=None, genres=None, most_type=None): def get_most_played_movies(self, limit=1000, languages=None, genres=None, most_type=None):
return self._make_items_request( return self._make_items_request(
url='https://api.trakt.tv/movies/played/%s' % ('weekly' if not most_type else most_type), url='https://api.trakt.tv/movies/played/%s' % ('weekly' if not most_type else most_type),
@ -456,6 +508,17 @@ class Trakt:
type_name='anticipated', type_name='anticipated',
) )
def get_recommended_movies(self, authenticate_user=None, limit=1000, languages=None, genres=None):
return self._make_items_request(
url='https://api.trakt.tv/recommendations/movies',
authenticate_user=authenticate_user,
limit=limit,
languages=languages,
object_name='movies',
type_name='recommended from {authenticate_user}',
genres=genres
)
def get_watchlist_movies(self, authenticate_user=None, limit=1000, languages=None): def get_watchlist_movies(self, authenticate_user=None, limit=1000, languages=None):
return self._make_items_request( return self._make_items_request(
url='https://api.trakt.tv/users/{authenticate_user}/watchlist/movies', url='https://api.trakt.tv/users/{authenticate_user}/watchlist/movies',

@ -55,6 +55,9 @@ class Config(object, metaclass=Singleton):
'profile': 'HD-1080p', 'profile': 'HD-1080p',
'root_folder': '/movies/' 'root_folder': '/movies/'
}, },
'omdb': {
'api_key': ''
},
'filters': { 'filters': {
'shows': { 'shows': {
'disabled_for': [], 'disabled_for': [],
@ -76,7 +79,8 @@ class Config(object, metaclass=Singleton):
'blacklist_title_keywords': [], 'blacklist_title_keywords': [],
'blacklisted_tmdb_ids': [], 'blacklisted_tmdb_ids': [],
'allowed_countries': [], 'allowed_countries': [],
'allowed_languages': [] 'allowed_languages': [],
'rating_limit':""
} }
}, },
'automatic': { 'automatic': {

@ -2,4 +2,5 @@ backoff==1.5.0
schedule==0.5.0 schedule==0.5.0
attrdict==2.0.0 attrdict==2.0.0
click==6.7 click==6.7
requests~=2.18.4 requests~=2.20.0
pyfiglet

@ -7,6 +7,8 @@ import time
import click import click
import schedule import schedule
from pyfiglet import Figlet
############################################################ ############################################################
# INIT # INIT
############################################################ ############################################################
@ -172,21 +174,24 @@ def show(show_id, folder=None, no_search=False):
@app.command(help='Add multiple shows to Sonarr.') @app.command(help='Add multiple shows to Sonarr.')
@click.option('--list-type', '-t', @click.option('--list-type', '-t',
help='Trakt list to process. For example, anticipated, trending, popular, watched, played, watchlist ' help='Trakt list to process. For example, anticipated, trending, popular, person, watched, played, '
'or any URL to a list', required=True) 'recommended, watchlist or any URL to a list', required=True)
@click.option('--add-limit', '-l', default=0, help='Limit number of shows added to Sonarr.', show_default=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('--add-delay', '-d', default=2.5, help='Seconds between each add request to Sonarr.', show_default=True)
@click.option('--sort', '-s', default='votes', type=click.Choice(['votes', 'rating', 'release']), @click.option('--sort', '-s', default='votes', type=click.Choice(['votes', 'rating', 'release']),
help='Sort list to process.') help='Sort list to process.')
@click.option('--genre', '-g', default=None, help='Only add shows from this genre to Sonarr.') @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('--folder', '-f', default=None, help='Add shows with this root folder to Sonarr.')
@click.option('--actor', '-a', default=None, help='Only add movies from this actor to Radarr.')
@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.')
@click.option('--authenticate-user', @click.option('--authenticate-user',
help='Specify which user to authenticate with to retrieve Trakt lists. Default: first user in the config') help='Specify which user to authenticate with to retrieve Trakt lists. Default: first user in the config')
@click.option('--ignore-blacklist', is_flag=True, help='Ignores the blacklist when running the command.') @click.option('--ignore-blacklist', is_flag=True, help='Ignores the blacklist when running the command.')
def shows(list_type, add_limit=0, add_delay=2.5, sort='votes', genre=None, folder=None, no_search=False, @click.option('--remove-rejected-from-recommended', is_flag=True,
notifications=False, authenticate_user=None, ignore_blacklist=False): help='Removes rejected/existing shows from recommended.')
def shows(list_type, add_limit=0, add_delay=2.5, sort='votes', genre=None, folder=None, actor=None, no_search=False,
notifications=False, authenticate_user=None, ignore_blacklist=False, remove_rejected_from_recommended=False):
from media.sonarr import Sonarr from media.sonarr import Sonarr
from media.trakt import Trakt from media.trakt import Trakt
from helpers import misc as misc_helper from helpers import misc as misc_helper
@ -222,6 +227,15 @@ def shows(list_type, add_limit=0, add_delay=2.5, sort='votes', genre=None, folde
trakt_objects_list = trakt.get_trending_shows(genres=genre, languages=cfg.filters.shows.allowed_languages) trakt_objects_list = trakt.get_trending_shows(genres=genre, languages=cfg.filters.shows.allowed_languages)
elif list_type.lower() == 'popular': elif list_type.lower() == 'popular':
trakt_objects_list = trakt.get_popular_shows(genres=genre, languages=cfg.filters.shows.allowed_languages) trakt_objects_list = trakt.get_popular_shows(genres=genre, languages=cfg.filters.shows.allowed_languages)
elif list_type.lower() == 'person':
if not actor:
log.error("You must specify an actor with the --actor / -a parameter when using the person list type!")
return None
trakt_objects_list = trakt.get_person_shows(person=actor, genres=genre,
languages=cfg.filters.shows.allowed_languages)
elif list_type.lower() == 'recommended':
trakt_objects_list = trakt.get_recommended_shows(authenticate_user, genres=genre,
languages=cfg.filters.shows.allowed_languages)
elif list_type.lower().startswith('played'): elif list_type.lower().startswith('played'):
most_type = misc_helper.substring_after(list_type.lower(), "_") most_type = misc_helper.substring_after(list_type.lower(), "_")
trakt_objects_list = trakt.get_most_played_shows(genres=genre, languages=cfg.filters.shows.allowed_languages, trakt_objects_list = trakt.get_most_played_shows(genres=genre, languages=cfg.filters.shows.allowed_languages,
@ -245,8 +259,14 @@ def shows(list_type, add_limit=0, add_delay=2.5, sort='votes', genre=None, folde
else: else:
log.info("Retrieved Trakt %s shows list, shows found: %d", list_type, len(trakt_objects_list)) log.info("Retrieved Trakt %s shows list, shows found: %d", list_type, len(trakt_objects_list))
# set remove_rejected_recommended to False if this is not the recommended list
if list_type.lower() != 'recommended':
remove_rejected_from_recommended = False
# build filtered series list without series that exist in sonarr # build filtered series list without series that exist in sonarr
processed_series_list = sonarr_helper.remove_existing_series(pvr_objects_list, trakt_objects_list) processed_series_list = sonarr_helper.remove_existing_series(pvr_objects_list, trakt_objects_list,
callback_remove_recommended
if remove_rejected_from_recommended else None)
if processed_series_list is None: if processed_series_list is None:
log.error("Aborting due to failure to remove existing Sonarr shows from retrieved Trakt shows list") log.error("Aborting due to failure to remove existing Sonarr shows from retrieved Trakt shows list")
if notifications: if notifications:
@ -279,7 +299,9 @@ def shows(list_type, add_limit=0, add_delay=2.5, sort='votes', genre=None, folde
continue continue
# check if series passes out blacklist criteria inspection # check if series passes out blacklist criteria inspection
if not trakt_helper.is_show_blacklisted(series, cfg.filters.shows, ignore_blacklist): if not trakt_helper.is_show_blacklisted(series, cfg.filters.shows, ignore_blacklist,
callback_remove_recommended
if remove_rejected_from_recommended else None):
log.info("Adding: %s | Genres: %s | Network: %s | Country: %s", series['show']['title'], log.info("Adding: %s | Genres: %s | Network: %s | Country: %s", series['show']['title'],
', '.join(series['show']['genres']), series['show']['network'], ', '.join(series['show']['genres']), series['show']['network'],
series['show']['country'].upper()) series['show']['country'].upper())
@ -366,27 +388,31 @@ def movie(movie_id, folder=None, no_search=False):
@app.command(help='Add multiple movies to Radarr.') @app.command(help='Add multiple movies to Radarr.')
@click.option('--list-type', '-t', @click.option('--list-type', '-t',
help='Trakt list to process. For example, anticipated, trending, popular, boxoffice, watched, played, ' help='Trakt list to process. For example, anticipated, trending, popular, boxoffice, person, watched, '
'watchlist or any URL to a list', 'recommended, played, watchlist or any URL to a list', required=True)
required=True)
@click.option('--add-limit', '-l', default=0, help='Limit number of movies added to Radarr.', show_default=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('--add-delay', '-d', default=2.5, help='Seconds between each add request to Radarr.', show_default=True)
@click.option('--sort', '-s', default='votes', type=click.Choice(['votes', 'rating', 'release']), @click.option('--sort', '-s', default='votes', type=click.Choice(['votes', 'rating', 'release']),
help='Sort list to process.') help='Sort list to process.')
@click.option('--rating','-r',default=None,type=(int),help='Set a minimum rating threshold (according to Rotten Tomatoes)')
@click.option('--genre', '-g', default=None, help='Only add movies from this genre to Radarr.') @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('--folder', '-f', default=None, help='Add movies with this root folder to Radarr.')
@click.option('--actor', '-a', default=None, help='Only add movies from this actor to Radarr.')
@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.')
@click.option('--authenticate-user', @click.option('--authenticate-user',
help='Specify which user to authenticate with to retrieve Trakt lists. Default: first user in the config.') help='Specify which user to authenticate with to retrieve Trakt lists. Default: first user in the config.')
@click.option('--ignore-blacklist', is_flag=True, help='Ignores the blacklist when running the command.') @click.option('--ignore-blacklist', is_flag=True, help='Ignores the blacklist when running the command.')
def movies(list_type, add_limit=0, add_delay=2.5, sort='votes', genre=None, folder=None, no_search=False, @click.option('--remove-rejected-from-recommended', is_flag=True,
notifications=False, authenticate_user=None, ignore_blacklist=False): help='Removes rejected/existing movies from recommended.')
def movies(list_type, add_limit=0, add_delay=2.5, sort='votes', rating=None, genre=None, folder=None, actor=None, no_search=False,
notifications=False, authenticate_user=None, ignore_blacklist=False, remove_rejected_from_recommended=False):
from media.radarr import Radarr from media.radarr import Radarr
from media.trakt import Trakt from media.trakt import Trakt
from helpers import misc as misc_helper from helpers import misc as misc_helper
from helpers import radarr as radarr_helper from helpers import radarr as radarr_helper
from helpers import trakt as trakt_helper from helpers import trakt as trakt_helper
from helpers import rating as rating_helper
added_movies = 0 added_movies = 0
@ -418,6 +444,16 @@ def movies(list_type, add_limit=0, add_delay=2.5, sort='votes', genre=None, fold
trakt_objects_list = trakt.get_popular_movies(genres=genre, languages=cfg.filters.movies.allowed_languages) trakt_objects_list = trakt.get_popular_movies(genres=genre, languages=cfg.filters.movies.allowed_languages)
elif list_type.lower() == 'boxoffice': elif list_type.lower() == 'boxoffice':
trakt_objects_list = trakt.get_boxoffice_movies() trakt_objects_list = trakt.get_boxoffice_movies()
elif list_type.lower() == 'person':
if not actor:
log.error("You must specify an actor with the --actor / -a parameter when using the person list type!")
return None
trakt_objects_list = trakt.get_person_movies(person=actor, genres=genre,
languages=cfg.filters.movies.allowed_languages)
elif list_type.lower() == 'recommended':
trakt_objects_list = trakt.get_recommended_movies(authenticate_user, genres=genre,
languages=cfg.filters.movies.allowed_languages)
elif list_type.lower().startswith('played'): elif list_type.lower().startswith('played'):
most_type = misc_helper.substring_after(list_type.lower(), "_") most_type = misc_helper.substring_after(list_type.lower(), "_")
trakt_objects_list = trakt.get_most_played_movies(genres=genre, languages=cfg.filters.movies.allowed_languages, trakt_objects_list = trakt.get_most_played_movies(genres=genre, languages=cfg.filters.movies.allowed_languages,
@ -441,8 +477,14 @@ def movies(list_type, add_limit=0, add_delay=2.5, sort='votes', genre=None, fold
else: else:
log.info("Retrieved Trakt %s movies list, movies found: %d", list_type, len(trakt_objects_list)) log.info("Retrieved Trakt %s movies list, movies found: %d", list_type, len(trakt_objects_list))
# set remove_rejected_recommended to False if this is not the recommended list
if list_type.lower() != 'recommended':
remove_rejected_from_recommended = False
# build filtered movie list without movies that exist in radarr # build filtered movie list without movies that exist in radarr
processed_movies_list = radarr_helper.remove_existing_movies(pvr_objects_list, trakt_objects_list) processed_movies_list = radarr_helper.remove_existing_movies(pvr_objects_list, trakt_objects_list,
callback_remove_recommended
if remove_rejected_from_recommended else None)
if processed_movies_list is None: if processed_movies_list is None:
log.error("Aborting due to failure to remove existing Radarr movies from retrieved Trakt movies list") log.error("Aborting due to failure to remove existing Radarr movies from retrieved Trakt movies list")
if notifications: if notifications:
@ -475,19 +517,33 @@ def movies(list_type, add_limit=0, add_delay=2.5, sort='votes', genre=None, fold
continue continue
# check if movie passes out blacklist criteria inspection # check if movie passes out blacklist criteria inspection
if not trakt_helper.is_movie_blacklisted(movie, cfg.filters.movies, ignore_blacklist): if not trakt_helper.is_movie_blacklisted(movie, cfg.filters.movies, ignore_blacklist,
log.info("Adding: %s (%d) | Genres: %s | Country: %s", movie['movie']['title'], movie['movie']['year'], callback_remove_recommended if remove_rejected_from_recommended
', '.join(movie['movie']['genres']), movie['movie']['country'].upper()) else None):
# add movie to radarr # Assuming the movie is not blacklisted, proceed to pull RT score if the user wishes to restrict
if radarr.add_movie(movie['movie']['ids']['tmdb'], movie['movie']['title'], movie['movie']['year'], movieRating = None
movie['movie']['ids']['slug'], profile_id, cfg.radarr.root_folder, not no_search): if (rating != None and cfg['omdb']['api_key'] != ''):
log.info("ADDED %s (%d)", movie['movie']['title'], movie['movie']['year']) movieRating = rating_helper.get_rating(cfg['omdb']['api_key'],movie)
if notifications: if (movieRating == -1):
callback_notify({'event': 'add_movie', 'list_type': list_type, 'movie': movie['movie']}) log.debug("Skipping: %s because it did not have a rating/lacked imdbID",
added_movies += 1 movie['movie']['title'])
continue
if (rating == None or movieRating >= rating):
log.info("Adding: %s (%d) | Genres: %s | Country: %s", movie['movie']['title'], movie['movie']['year'],
', '.join(movie['movie']['genres']), movie['movie']['country'].upper())
# add movie to radarr
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'])
else: else:
log.error("FAILED adding %s (%d)", movie['movie']['title'], movie['movie']['year']) log.info("SKIPPING: %s (%d) | Genres: %s | Country: %s", movie['movie']['title'],
movie['movie']['year'],
', '.join(movie['movie']['genres']), movie['movie']['country'].upper())
# stop adding movies, if added_movies >= add_limit # stop adding movies, if added_movies >= add_limit
if add_limit and added_movies >= add_limit: if add_limit and added_movies >= add_limit:
break break
@ -508,9 +564,28 @@ def movies(list_type, add_limit=0, add_delay=2.5, sort='votes', genre=None, fold
############################################################ ############################################################
# AUTOMATIC # CALLBACKS
############################################################ ############################################################
def callback_remove_recommended(media_type, media_info):
from media.trakt import Trakt
trakt = Trakt(cfg)
if not media_info[media_type]['title'] or not media_info[media_type]['year']:
log.debug("Skipping removing %s item from recommended list as no title/year was available:\n%s", media_type,
media_info)
return
media_name = '%s (%d)' % (media_info[media_type]['title'], media_info[media_type]['year'])
if trakt.remove_recommended_item(media_type, media_info[media_type]['ids']['trakt']):
log.info("Removed rejected recommended %s: %s", media_type, media_name)
else:
log.info("FAILED removing rejected recommended %s: %s", media_type, media_name)
def callback_notify(data): def callback_notify(data):
log.debug("Received callback data: %s", data) log.debug("Received callback data: %s", data)
@ -536,6 +611,11 @@ def callback_notify(data):
return return
############################################################
# AUTOMATIC
############################################################
def automatic_shows(add_delay=2.5, sort='votes', no_search=False, notifications=False, ignore_blacklist=False): def automatic_shows(add_delay=2.5, sort='votes', no_search=False, notifications=False, ignore_blacklist=False):
from media.trakt import Trakt from media.trakt import Trakt
@ -625,7 +705,7 @@ def automatic_shows(add_delay=2.5, sort='votes', no_search=False, notifications=
return return
def automatic_movies(add_delay=2.5, sort='votes', no_search=False, notifications=False, ignore_blacklist=False): def automatic_movies(add_delay=2.5, sort='votes', no_search=False, notifications=False, ignore_blacklist=False,rating_limit=None):
from media.trakt import Trakt from media.trakt import Trakt
total_movies_added = 0 total_movies_added = 0
@ -656,7 +736,7 @@ def automatic_movies(add_delay=2.5, sort='votes', no_search=False, notifications
# run movies # run movies
added_movies = movies.callback(list_type=list_type, add_limit=limit, added_movies = movies.callback(list_type=list_type, add_limit=limit,
add_delay=add_delay, sort=sort, no_search=no_search, add_delay=add_delay, sort=sort, no_search=no_search,
notifications=notifications) notifications=notifications,rating=rating_limit)
elif list_type.lower() == 'watchlist': elif list_type.lower() == 'watchlist':
for authenticate_user, limit in value.items(): for authenticate_user, limit in value.items():
if limit <= 0: if limit <= 0:
@ -674,7 +754,7 @@ def automatic_movies(add_delay=2.5, sort='votes', no_search=False, notifications
added_movies = movies.callback(list_type=list_type, add_limit=limit, added_movies = movies.callback(list_type=list_type, add_limit=limit,
add_delay=add_delay, sort=sort, no_search=no_search, add_delay=add_delay, sort=sort, no_search=no_search,
notifications=notifications, authenticate_user=authenticate_user, notifications=notifications, authenticate_user=authenticate_user,
ignore_blacklist=local_ignore_blacklist) ignore_blacklist=local_ignore_blacklist,rating=rating_limit)
elif list_type.lower() == 'lists': elif list_type.lower() == 'lists':
for list, v in value.items(): for list, v in value.items():
if isinstance(v, dict): if isinstance(v, dict):
@ -693,7 +773,7 @@ def automatic_movies(add_delay=2.5, sort='votes', no_search=False, notifications
added_movies = movies.callback(list_type=list, add_limit=limit, added_movies = movies.callback(list_type=list, add_limit=limit,
add_delay=add_delay, sort=sort, no_search=no_search, add_delay=add_delay, sort=sort, no_search=no_search,
notifications=notifications, authenticate_user=authenticate_user, notifications=notifications, authenticate_user=authenticate_user,
ignore_blacklist=local_ignore_blacklist) ignore_blacklist=local_ignore_blacklist,rating=rating_limit)
if added_movies is None: if added_movies is None:
log.error("Failed adding movies from Trakt's %s list", list_type) log.error("Failed adding movies from Trakt's %s list", list_type)
@ -734,7 +814,8 @@ def run(add_delay=2.5, sort='votes', no_search=False, run_now=False, no_notifica
sort, sort,
no_search, no_search,
not no_notifications, not no_notifications,
ignore_blacklist ignore_blacklist,
int(cfg.filters.movies.rating_limit) if cfg.filters.movies.rating_limit != "" else None
) )
if run_now: if run_now:
movie_schedule.run() movie_schedule.run()
@ -799,18 +880,18 @@ def exit_handler(signum, frame):
############################################################ ############################################################
if __name__ == "__main__": if __name__ == "__main__":
print("""
,--. ,--. ,--.
,-' '-.,--.--. ,--,--.| |,-.,-' '-. ,--,--.,--.--.,--.--.
'-. .-'| .--'' ,-. || /'-. .-'' ,-. || .--'| .--'
| | | | \ '-' || \ \ | | \ '-' || | | |
`--' `--' `--`--'`--'`--' `--' `--`--'`--' `--'
print("")
f = Figlet(font='graffiti')
print(f.renderText('traktarr'))
print("""
######################################################################### #########################################################################
# Author: l3uddz # # Author: l3uddz #
# URL: https://github.com/l3uddz/traktarr # # URL: https://github.com/l3uddz/traktarr #
# -- # # -- #
# Part of the Cloudbox project: https://cloudbox.rocks # # Part of the Cloudbox project: https://cloudbox.works #
######################################################################### #########################################################################
# GNU General Public License v3.0 # # GNU General Public License v3.0 #
######################################################################### #########################################################################

Loading…
Cancel
Save