diff --git a/.dockerignore b/.dockerignore index 82cbdf1..8abf5bc 100644 --- a/.dockerignore +++ b/.dockerignore @@ -22,6 +22,10 @@ __pycache__/ # configs *.cfg *.json +*.json.sample + +# documents +*.md # generators *.bat diff --git a/LICENSE b/LICENSE.md similarity index 100% rename from LICENSE rename to LICENSE.md diff --git a/README.md b/README.md index 03d96cc..b05fc96 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ -[![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) +w[![made-with-python](https://img.shields.io/badge/Made%20with-Python-blue.svg)](https://www.python.org/) +[![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) -[![Discord](https://img.shields.io/discord/381077432285003776.svg)](https://discord.gg/xmNYmSJ) + # traktarr @@ -10,7 +11,7 @@ Types of Trakt lists supported: -- Official Trakt lists +- Official Trakt Lists - Trending @@ -20,13 +21,17 @@ Types of Trakt lists supported: - Boxoffice -- Public lists + - Most Watched + + - Most Played + +- Public Lists -- Private lists* +- Private Lists* - Watchlist - - Custom list(s) + - Custom List(s) \* Support for multiple (authenticated) users. @@ -34,7 +39,6 @@ Types of Trakt lists supported: -- [traktarr](#traktarr) - [Demo](#demo) - [Requirements](#requirements) - [Installation](#installation) @@ -69,8 +73,9 @@ Types of Trakt lists supported: - [Movies (Multiple Movies)](#movies-multiple-movies) - [Show (Single Show)](#show-single-show) - [Shows (Multiple Shows)](#shows-multiple-shows) - - [Examples (Manual)](#examples-manual) - + - [Examples (CLI)](#examples-cli) + - [Movies](#movies) + - [Shows](#shows) --- @@ -213,7 +218,8 @@ You can repeat this process for as many users as you like. "blacklisted_max_year": 2019, "blacklisted_min_runtime": 60, "blacklisted_min_year": 2000, - "blacklisted_tmdb_ids": [] + "blacklisted_tmdb_ids": [], + "rating_limit": "" }, "shows": { "disabled_for": [], @@ -286,6 +292,9 @@ You can repeat this process for as many users as you like. "trakt": { "client_id": "", "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. - - Default is `false` (keep it off unless your having issues). +`debug` - Toggle debug messages in the log. Default is `false`. + + - Set to `true`, if you are having issues and want to diagnose why. ## Automatic @@ -308,7 +318,7 @@ Used for automatic / scheduled traktarr tasks. 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 "automatic": { @@ -318,27 +328,51 @@ _Note: These settings are only needed if you plan to use traktarr on a schedule "interval": 24, "popular": 3, "trending": 2, + "watched": 2, + "played_all": 2, "watchlist": {}, - "lists": {} + "lists": {}, }, "shows": { "anticipated": 10, "interval": 48, "popular": 1, "trending": 2, + "watched_monthly": 2, + "played": 2, "watchlist": {}, "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 @@ -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_min_runtime": 60, "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. -``` - "disabled_for": [ - "anticipated", - "watchlist:user1", - "list:http://url-to-list" - ], -``` +- Example: -`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.) +- [List of available language codes](list_of_language_codes.md). + `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 @@ -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: @@ -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. + - 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 @@ -603,13 +663,14 @@ Currently, only Pushover and Slack are supported. More will be added later. }, ``` -`verbose` - toggle detailed notifications. - - Default is `true` (keep it off unless your having issues). +`verbose` - Toggle detailed notifications. Default is `true`. + + - 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 -`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). @@ -618,7 +679,7 @@ _Note: The key name (i.e the name right under notifications) can be anything, bu ### 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"`._ @@ -665,7 +726,7 @@ Sonarr configuration. `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. @@ -726,9 +787,18 @@ Trakt Authentication info: `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 ## 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/` -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` @@ -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: +\* 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. - [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. +traktarr run --help ``` -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) @@ -830,24 +933,81 @@ Usage: traktarr movies [OPTIONS] Add multiple movies to Radarr. Options: - -t, --list-type TEXT Trakt list to process. For example, anticipated, - trending, popular, boxoffice, watchlist or any URL - to a 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. - [default: 2.5] - -g, --genre TEXT Only add movies from this genre to Radarr. - -f, --folder TEXT Add movies with this root folder to Radarr. - --no-search Disable search when adding movies to Radarr. - --notifications Send notifications. - --ignore-blacklist Ignores the blacklist when running the command. - --authenticate-user TEXT Specify which user to authenticate with to - retrieve Trakt lists. Default: first user in the - config. - --help Show this message and exit. + -t, --list-type TEXT Trakt list to process. For example, + anticipated, trending, popular, boxoffice, + watched, played, watchlist or any URL to a + 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. + [default: 2.5] + -s, --sort [votes|rating|release] + Sort list to process. + -r, --rating INTEGER Only add movies above this rating according to Rotten Tomatoese Score + [default: 0] + -g, --genre TEXT Only add movies from this genre to Radarr. + -f, --folder TEXT Add movies with this root folder to Radarr. + --no-search Disable search when adding movies to Radarr. + --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) ``` @@ -883,25 +1043,79 @@ Usage: traktarr shows [OPTIONS] Add multiple shows to Sonarr. Options: - -t, --list-type TEXT Trakt list to process. For example, anticipated, - trending, popular, watchlist or any URL to a 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. - [default: 2.5] - -g, --genre TEXT Only add shows from this genre to Sonarr. - -f, --folder TEXT Add shows with this root folder to Sonarr. - --no-search Disable search when adding shows to Sonarr. - --notifications Send notifications. - --ignore-blacklist Ignores the blacklist when running the command. - --authenticate-user TEXT Specify which user to authenticate with to - retrieve Trakt lists. Default: first user in the - config - --help Show this message and exit. -``` - -## Examples (Manual) + -t, --list-type TEXT Trakt list to process. For example, + anticipated, trending, popular, watched, + played, watchlist or any URL to a 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. + [default: 2.5] + -s, --sort [votes|rating|release] + Sort list to process. + -g, --genre TEXT Only add shows from this genre to Sonarr. + -f, --folder TEXT Add shows with this root folder to Sonarr. + --no-search Disable search when adding shows to Sonarr. + --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`, `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)": @@ -909,43 +1123,102 @@ Options: 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 ``` -- 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 ``` -- 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 ``` -## Support on Beerpay -Hey dude! Help me out for a couple of :beers:! +- Add movies, from the trending list, with a minimum rating of 80% on Rotten Tomatoes. + + ``` + 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) \ No newline at end of file + - BTC: 3CiHME1HZQsNNcDL6BArG7PbZLa8zUUgjL diff --git a/config.json.sample b/config.json.sample new file mode 100644 index 0000000..a79bd65 --- /dev/null +++ b/config.json.sample @@ -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": "" + } +} diff --git a/helpers/radarr.py b/helpers/radarr.py index c8732f2..bf6df50 100644 --- a/helpers/radarr.py +++ b/helpers/radarr.py @@ -17,7 +17,7 @@ def movies_to_tmdb_dict(radarr_movies): return None -def remove_existing_movies(radarr_movies, trakt_movies): +def remove_existing_movies(radarr_movies, trakt_movies, callback=None): new_movies_list = [] 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: 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) + if callback: + callback('movie', tmp) continue # check if movie exists in processed_movies if tmp['movie']['ids']['tmdb'] in processed_movies: log.debug("Removing existing movie: %s", tmp['movie']['title']) + if callback: + callback('movie', tmp) continue new_movies_list.append(tmp) diff --git a/helpers/rating.py b/helpers/rating.py new file mode 100644 index 0000000..353903b --- /dev/null +++ b/helpers/rating.py @@ -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 diff --git a/helpers/sonarr.py b/helpers/sonarr.py index 52c2f91..99d34ca 100644 --- a/helpers/sonarr.py +++ b/helpers/sonarr.py @@ -48,7 +48,7 @@ def series_to_tvdb_dict(sonarr_series): return None -def remove_existing_series(sonarr_series, trakt_series): +def remove_existing_series(sonarr_series, trakt_series, callback=None): new_series_list = [] 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: 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) + if callback: + callback('show', tmp) continue # check if show exists in processed_series if tmp['show']['ids']['tvdb'] in processed_series: log.debug("Removing existing show: %s", tmp['show']['title']) + if callback: + callback('show', tmp) continue new_series_list.append(tmp) diff --git a/helpers/trakt.py b/helpers/trakt.py index 484d28a..289b4dd 100644 --- a/helpers/trakt.py +++ b/helpers/trakt.py @@ -106,7 +106,7 @@ def blacklisted_show_id(show, blacklisted_ids): 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: return False @@ -125,6 +125,10 @@ def is_show_blacklisted(show, blacklist_settings, ignore_blacklist): blacklisted = True if blacklisted_show_id(show, blacklist_settings.blacklisted_tvdb_ids): blacklisted = True + + if blacklisted and callback: + callback('show', show) + except Exception: log.exception("Exception determining if show was blacklisted %s: ", show) return blacklisted @@ -231,7 +235,7 @@ def blacklisted_movie_id(movie, blacklisted_ids): 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: return False @@ -250,6 +254,10 @@ def is_movie_blacklisted(movie, blacklist_settings, ignore_blacklist): blacklisted = True if blacklisted_movie_id(movie, blacklist_settings.blacklisted_tmdb_ids): blacklisted = True + + if blacklisted and callback: + callback('movie', movie) + except Exception: log.exception("Exception determining if movie was blacklisted %s: ", movie) return blacklisted diff --git a/list_of_country_codes.md b/list_of_country_codes.md new file mode 100644 index 0000000..c57a9a6 --- /dev/null +++ b/list_of_country_codes.md @@ -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) diff --git a/list_of_language_codes.md b/list_of_language_codes.md new file mode 100644 index 0000000..45c74d3 --- /dev/null +++ b/list_of_language_codes.md @@ -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) diff --git a/list_of_movie_genres.md b/list_of_movie_genres.md new file mode 100644 index 0000000..feee12d --- /dev/null +++ b/list_of_movie_genres.md @@ -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` diff --git a/list_of_show_genres.md b/list_of_show_genres.md new file mode 100644 index 0000000..b7dd63e --- /dev/null +++ b/list_of_show_genres.md @@ -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` diff --git a/media/trakt.py b/media/trakt.py index 133f117..84fd70c 100644 --- a/media/trakt.py +++ b/media/trakt.py @@ -20,14 +20,17 @@ class Trakt: # 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) if authenticate_user: url = url.replace('{authenticate_user}', authenticate_user) # 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 Payload: %s", payload) log.debug("Request User: %s", authenticate_user) @@ -82,13 +85,21 @@ class Trakt: if req.status_code == 200: resp_json = req.json() - - 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) + if type_name == 'person' and 'cast' in resp_json: + # handle person results + for item in resp_json['cast']: + 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) + 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 if total_pages == 0: @@ -130,6 +141,16 @@ class Trakt: log.exception("Exception validating client_id: ") 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 ############################################################ @@ -343,6 +364,16 @@ class Trakt: 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): return self._make_items_request( url='https://api.trakt.tv/shows/played/%s' % ('weekly' if not most_type else most_type), @@ -363,6 +394,17 @@ class Trakt: 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): return self._make_items_request( url='https://api.trakt.tv/users/{authenticate_user}/watchlist/shows', @@ -427,6 +469,16 @@ class Trakt: 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): return self._make_items_request( 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', ) + 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): return self._make_items_request( url='https://api.trakt.tv/users/{authenticate_user}/watchlist/movies', diff --git a/misc/config.py b/misc/config.py index e3fbfba..4ddfe5f 100644 --- a/misc/config.py +++ b/misc/config.py @@ -55,6 +55,9 @@ class Config(object, metaclass=Singleton): 'profile': 'HD-1080p', 'root_folder': '/movies/' }, + 'omdb': { + 'api_key': '' + }, 'filters': { 'shows': { 'disabled_for': [], @@ -76,7 +79,8 @@ class Config(object, metaclass=Singleton): 'blacklist_title_keywords': [], 'blacklisted_tmdb_ids': [], 'allowed_countries': [], - 'allowed_languages': [] + 'allowed_languages': [], + 'rating_limit':"" } }, 'automatic': { diff --git a/requirements.txt b/requirements.txt index b865c62..8066002 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,5 @@ backoff==1.5.0 schedule==0.5.0 attrdict==2.0.0 click==6.7 -requests~=2.18.4 +requests~=2.20.0 +pyfiglet diff --git a/traktarr.py b/traktarr.py index 0e12cab..b838b1f 100755 --- a/traktarr.py +++ b/traktarr.py @@ -7,6 +7,8 @@ import time import click import schedule +from pyfiglet import Figlet + ############################################################ # INIT ############################################################ @@ -172,21 +174,24 @@ def show(show_id, folder=None, no_search=False): @app.command(help='Add multiple shows to Sonarr.') @click.option('--list-type', '-t', - help='Trakt list to process. For example, anticipated, trending, popular, watched, played, watchlist ' - 'or any URL to a list', required=True) + help='Trakt list to process. For example, anticipated, trending, popular, person, watched, played, ' + '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-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']), help='Sort list to process.') @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('--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('--notifications', is_flag=True, help='Send notifications.') @click.option('--authenticate-user', 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.') -def shows(list_type, add_limit=0, add_delay=2.5, sort='votes', genre=None, folder=None, no_search=False, - notifications=False, authenticate_user=None, ignore_blacklist=False): +@click.option('--remove-rejected-from-recommended', is_flag=True, + 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.trakt import Trakt 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) elif list_type.lower() == 'popular': 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'): 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, @@ -245,8 +259,14 @@ def shows(list_type, add_limit=0, add_delay=2.5, sort='votes', genre=None, folde else: 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 - 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: log.error("Aborting due to failure to remove existing Sonarr shows from retrieved Trakt shows list") if notifications: @@ -279,7 +299,9 @@ def shows(list_type, add_limit=0, add_delay=2.5, sort='votes', genre=None, folde continue # 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'], ', '.join(series['show']['genres']), series['show']['network'], 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.') @click.option('--list-type', '-t', - help='Trakt list to process. For example, anticipated, trending, popular, boxoffice, watched, played, ' - 'watchlist or any URL to a list', - required=True) + help='Trakt list to process. For example, anticipated, trending, popular, boxoffice, person, watched, ' + 'recommended, played, watchlist or any URL to a 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('--sort', '-s', default='votes', type=click.Choice(['votes', 'rating', 'release']), 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('--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('--notifications', is_flag=True, help='Send notifications.') @click.option('--authenticate-user', 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.') -def movies(list_type, add_limit=0, add_delay=2.5, sort='votes', genre=None, folder=None, no_search=False, - notifications=False, authenticate_user=None, ignore_blacklist=False): +@click.option('--remove-rejected-from-recommended', is_flag=True, + 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.trakt import Trakt from helpers import misc as misc_helper from helpers import radarr as radarr_helper from helpers import trakt as trakt_helper + from helpers import rating as rating_helper 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) elif list_type.lower() == 'boxoffice': 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'): 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, @@ -441,8 +477,14 @@ def movies(list_type, add_limit=0, add_delay=2.5, sort='votes', genre=None, fold else: 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 - 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: log.error("Aborting due to failure to remove existing Radarr movies from retrieved Trakt movies list") if notifications: @@ -475,19 +517,33 @@ def movies(list_type, add_limit=0, add_delay=2.5, sort='votes', genre=None, fold continue # check if movie passes out blacklist criteria inspection - 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'], - ', '.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 + if not trakt_helper.is_movie_blacklisted(movie, cfg.filters.movies, ignore_blacklist, + callback_remove_recommended if remove_rejected_from_recommended + else None): + # Assuming the movie is not blacklisted, proceed to pull RT score if the user wishes to restrict + movieRating = None + if (rating != None and cfg['omdb']['api_key'] != ''): + movieRating = rating_helper.get_rating(cfg['omdb']['api_key'],movie) + if (movieRating == -1): + log.debug("Skipping: %s because it did not have a rating/lacked imdbID", + 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: - 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 if add_limit and added_movies >= add_limit: 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): log.debug("Received callback data: %s", data) @@ -536,6 +611,11 @@ def callback_notify(data): return +############################################################ +# AUTOMATIC +############################################################ + + def automatic_shows(add_delay=2.5, sort='votes', no_search=False, notifications=False, ignore_blacklist=False): from media.trakt import Trakt @@ -625,7 +705,7 @@ def automatic_shows(add_delay=2.5, sort='votes', no_search=False, notifications= 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 total_movies_added = 0 @@ -656,7 +736,7 @@ def automatic_movies(add_delay=2.5, sort='votes', no_search=False, notifications # run movies added_movies = movies.callback(list_type=list_type, add_limit=limit, add_delay=add_delay, sort=sort, no_search=no_search, - notifications=notifications) + notifications=notifications,rating=rating_limit) elif list_type.lower() == 'watchlist': for authenticate_user, limit in value.items(): 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, add_delay=add_delay, sort=sort, no_search=no_search, notifications=notifications, authenticate_user=authenticate_user, - ignore_blacklist=local_ignore_blacklist) + ignore_blacklist=local_ignore_blacklist,rating=rating_limit) elif list_type.lower() == 'lists': for list, v in value.items(): 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, add_delay=add_delay, sort=sort, no_search=no_search, notifications=notifications, authenticate_user=authenticate_user, - ignore_blacklist=local_ignore_blacklist) + ignore_blacklist=local_ignore_blacklist,rating=rating_limit) if added_movies is None: 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, no_search, not no_notifications, - ignore_blacklist + ignore_blacklist, + int(cfg.filters.movies.rating_limit) if cfg.filters.movies.rating_limit != "" else None ) if run_now: movie_schedule.run() @@ -799,18 +880,18 @@ def exit_handler(signum, frame): ############################################################ if __name__ == "__main__": - print(""" - ,--. ,--. ,--. -,-' '-.,--.--. ,--,--.| |,-.,-' '-. ,--,--.,--.--.,--.--. -'-. .-'| .--'' ,-. || /'-. .-'' ,-. || .--'| .--' - | | | | \ '-' || \ \ | | \ '-' || | | | - `--' `--' `--`--'`--'`--' `--' `--`--'`--' `--' + print("") + + f = Figlet(font='graffiti') + print(f.renderText('traktarr')) + + print(""" ######################################################################### # Author: l3uddz # # 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 # #########################################################################