added arrapi

pull/309/head
meisnate12 3 years ago
parent 1d7d2a972c
commit 8f491e70c2

@ -10,8 +10,8 @@ from modules.letterboxd import LetterboxdAPI
from modules.mal import MyAnimeListAPI from modules.mal import MyAnimeListAPI
from modules.omdb import OMDbAPI from modules.omdb import OMDbAPI
from modules.plex import PlexAPI from modules.plex import PlexAPI
from modules.radarr import RadarrAPI from modules.radarr import Radarr
from modules.sonarr import SonarrAPI from modules.sonarr import Sonarr
from modules.tautulli import TautulliAPI from modules.tautulli import TautulliAPI
from modules.tmdb import TMDbAPI from modules.tmdb import TMDbAPI
from modules.trakttv import TraktAPI from modules.trakttv import TraktAPI
@ -292,7 +292,6 @@ class Config:
self.general["radarr"] = {} self.general["radarr"] = {}
self.general["radarr"]["url"] = check_for_attribute(self.data, "url", parent="radarr", var_type="url", default_is_none=True) self.general["radarr"]["url"] = check_for_attribute(self.data, "url", parent="radarr", var_type="url", default_is_none=True)
self.general["radarr"]["token"] = check_for_attribute(self.data, "token", parent="radarr", default_is_none=True) self.general["radarr"]["token"] = check_for_attribute(self.data, "token", parent="radarr", default_is_none=True)
self.general["radarr"]["version"] = check_for_attribute(self.data, "version", parent="radarr", test_list=radarr_versions, default="v3")
self.general["radarr"]["add"] = check_for_attribute(self.data, "add", parent="radarr", var_type="bool", default=False) self.general["radarr"]["add"] = check_for_attribute(self.data, "add", parent="radarr", var_type="bool", default=False)
self.general["radarr"]["root_folder_path"] = check_for_attribute(self.data, "root_folder_path", parent="radarr", default_is_none=True) self.general["radarr"]["root_folder_path"] = check_for_attribute(self.data, "root_folder_path", parent="radarr", default_is_none=True)
self.general["radarr"]["monitor"] = check_for_attribute(self.data, "monitor", parent="radarr", var_type="bool", default=True) self.general["radarr"]["monitor"] = check_for_attribute(self.data, "monitor", parent="radarr", var_type="bool", default=True)
@ -304,7 +303,6 @@ class Config:
self.general["sonarr"] = {} self.general["sonarr"] = {}
self.general["sonarr"]["url"] = check_for_attribute(self.data, "url", parent="sonarr", var_type="url", default_is_none=True) self.general["sonarr"]["url"] = check_for_attribute(self.data, "url", parent="sonarr", var_type="url", default_is_none=True)
self.general["sonarr"]["token"] = check_for_attribute(self.data, "token", parent="sonarr", default_is_none=True) self.general["sonarr"]["token"] = check_for_attribute(self.data, "token", parent="sonarr", default_is_none=True)
self.general["sonarr"]["version"] = check_for_attribute(self.data, "version", parent="sonarr", test_list=sonarr_versions, default="v3")
self.general["sonarr"]["add"] = check_for_attribute(self.data, "add", parent="sonarr", var_type="bool", default=False) self.general["sonarr"]["add"] = check_for_attribute(self.data, "add", parent="sonarr", var_type="bool", default=False)
self.general["sonarr"]["root_folder_path"] = check_for_attribute(self.data, "root_folder_path", parent="sonarr", default_is_none=True) self.general["sonarr"]["root_folder_path"] = check_for_attribute(self.data, "root_folder_path", parent="sonarr", default_is_none=True)
self.general["sonarr"]["monitor"] = check_for_attribute(self.data, "monitor", parent="sonarr", test_list=sonarr_monitors, default="all") self.general["sonarr"]["monitor"] = check_for_attribute(self.data, "monitor", parent="sonarr", test_list=sonarr_monitors, default="all")
@ -467,7 +465,6 @@ class Config:
try: try:
radarr_params["url"] = check_for_attribute(lib, "url", parent="radarr", var_type="url", default=self.general["radarr"]["url"], req_default=True, save=False) radarr_params["url"] = check_for_attribute(lib, "url", parent="radarr", var_type="url", default=self.general["radarr"]["url"], req_default=True, save=False)
radarr_params["token"] = check_for_attribute(lib, "token", parent="radarr", default=self.general["radarr"]["token"], req_default=True, save=False) radarr_params["token"] = check_for_attribute(lib, "token", parent="radarr", default=self.general["radarr"]["token"], req_default=True, save=False)
radarr_params["version"] = check_for_attribute(lib, "version", parent="radarr", test_list=radarr_versions, default=self.general["radarr"]["version"], save=False)
radarr_params["add"] = check_for_attribute(lib, "add", parent="radarr", var_type="bool", default=self.general["radarr"]["add"], save=False) radarr_params["add"] = check_for_attribute(lib, "add", parent="radarr", var_type="bool", default=self.general["radarr"]["add"], save=False)
radarr_params["root_folder_path"] = check_for_attribute(lib, "root_folder_path", parent="radarr", default=self.general["radarr"]["root_folder_path"], req_default=True, save=False) radarr_params["root_folder_path"] = check_for_attribute(lib, "root_folder_path", parent="radarr", default=self.general["radarr"]["root_folder_path"], req_default=True, save=False)
radarr_params["monitor"] = check_for_attribute(lib, "monitor", parent="radarr", var_type="bool", default=self.general["radarr"]["monitor"], save=False) radarr_params["monitor"] = check_for_attribute(lib, "monitor", parent="radarr", var_type="bool", default=self.general["radarr"]["monitor"], save=False)
@ -475,7 +472,7 @@ class Config:
radarr_params["quality_profile"] = check_for_attribute(lib, "quality_profile", parent="radarr", default=self.general["radarr"]["quality_profile"], req_default=True, save=False) radarr_params["quality_profile"] = check_for_attribute(lib, "quality_profile", parent="radarr", default=self.general["radarr"]["quality_profile"], req_default=True, save=False)
radarr_params["tag"] = check_for_attribute(lib, "search", parent="radarr", var_type="lower_list", default=self.general["radarr"]["tag"], default_is_none=True, save=False) radarr_params["tag"] = check_for_attribute(lib, "search", parent="radarr", var_type="lower_list", default=self.general["radarr"]["tag"], default_is_none=True, save=False)
radarr_params["search"] = check_for_attribute(lib, "search", parent="radarr", var_type="bool", default=self.general["radarr"]["search"], save=False) radarr_params["search"] = check_for_attribute(lib, "search", parent="radarr", var_type="bool", default=self.general["radarr"]["search"], save=False)
library.Radarr = RadarrAPI(radarr_params) library.Radarr = Radarr(radarr_params)
except Failed as e: except Failed as e:
util.print_multiline(e, error=True) util.print_multiline(e, error=True)
logger.info("") logger.info("")
@ -491,7 +488,6 @@ class Config:
try: try:
sonarr_params["url"] = check_for_attribute(lib, "url", parent="sonarr", var_type="url", default=self.general["sonarr"]["url"], req_default=True, save=False) sonarr_params["url"] = check_for_attribute(lib, "url", parent="sonarr", var_type="url", default=self.general["sonarr"]["url"], req_default=True, save=False)
sonarr_params["token"] = check_for_attribute(lib, "token", parent="sonarr", default=self.general["sonarr"]["token"], req_default=True, save=False) sonarr_params["token"] = check_for_attribute(lib, "token", parent="sonarr", default=self.general["sonarr"]["token"], req_default=True, save=False)
sonarr_params["version"] = check_for_attribute(lib, "version", parent="sonarr", test_list=sonarr_versions, default=self.general["sonarr"]["version"], save=False)
sonarr_params["add"] = check_for_attribute(lib, "add", parent="sonarr", var_type="bool", default=self.general["sonarr"]["add"], save=False) sonarr_params["add"] = check_for_attribute(lib, "add", parent="sonarr", var_type="bool", default=self.general["sonarr"]["add"], save=False)
sonarr_params["root_folder_path"] = check_for_attribute(lib, "root_folder_path", parent="sonarr", default=self.general["sonarr"]["root_folder_path"], req_default=True, save=False) sonarr_params["root_folder_path"] = check_for_attribute(lib, "root_folder_path", parent="sonarr", default=self.general["sonarr"]["root_folder_path"], req_default=True, save=False)
sonarr_params["monitor"] = check_for_attribute(lib, "monitor", parent="sonarr", test_list=sonarr_monitors, default=self.general["sonarr"]["monitor"], save=False) sonarr_params["monitor"] = check_for_attribute(lib, "monitor", parent="sonarr", test_list=sonarr_monitors, default=self.general["sonarr"]["monitor"], save=False)
@ -505,7 +501,7 @@ class Config:
sonarr_params["tag"] = check_for_attribute(lib, "search", parent="sonarr", var_type="lower_list", default=self.general["sonarr"]["tag"], default_is_none=True, save=False) sonarr_params["tag"] = check_for_attribute(lib, "search", parent="sonarr", var_type="lower_list", default=self.general["sonarr"]["tag"], default_is_none=True, save=False)
sonarr_params["search"] = check_for_attribute(lib, "search", parent="sonarr", var_type="bool", default=self.general["sonarr"]["search"], save=False) sonarr_params["search"] = check_for_attribute(lib, "search", parent="sonarr", var_type="bool", default=self.general["sonarr"]["search"], save=False)
sonarr_params["cutoff_search"] = check_for_attribute(lib, "cutoff_search", parent="sonarr", var_type="bool", default=self.general["sonarr"]["cutoff_search"], save=False) sonarr_params["cutoff_search"] = check_for_attribute(lib, "cutoff_search", parent="sonarr", var_type="bool", default=self.general["sonarr"]["cutoff_search"], save=False)
library.Sonarr = SonarrAPI(sonarr_params, library.Plex.language) library.Sonarr = Sonarr(sonarr_params)
except Failed as e: except Failed as e:
util.print_multiline(e, error=True) util.print_multiline(e, error=True)
logger.info("") logger.info("")

@ -1,7 +1,8 @@
import logging, requests import logging
from modules import util from modules import util
from modules.util import Failed from modules.util import Failed
from retrying import retry from arrapi import RadarrAPI
from arrapi.exceptions import ArrException, Invalid
logger = logging.getLogger("Plex Meta Manager") logger = logging.getLogger("Plex Meta Manager")
@ -12,121 +13,48 @@ availability_translation = {
"db": "preDB" "db": "preDB"
} }
class RadarrAPI: class Radarr:
def __init__(self, params): def __init__(self, params):
self.url = params["url"] self.url = params["url"]
self.token = params["token"] self.token = params["token"]
self.version = params["version"]
self.base_url = f"{self.url}/api{'/v3' if self.version == 'v3' else ''}/"
try: try:
result = requests.get(f"{self.base_url}system/status", params={"apikey": f"{self.token}"}).json() self.api = RadarrAPI(self.url, self.token)
except Exception: except ArrException as e:
util.print_stacktrace() raise Failed(e)
raise Failed(f"Radarr Error: Could not connect to Radarr at {self.url}")
if "error" in result and result["error"] == "Unauthorized":
raise Failed("Radarr Error: Invalid API Key")
if "version" not in result:
raise Failed("Radarr Error: Unexpected Response Check URL")
self.add = params["add"] self.add = params["add"]
self.root_folder_path = params["root_folder_path"] self.root_folder_path = params["root_folder_path"]
self.monitor = params["monitor"] self.monitor = params["monitor"]
self.availability = params["availability"] self.availability = params["availability"]
self.quality_profile_id = self.get_profile_id(params["quality_profile"]) self.quality_profile = params["quality_profile"]
self.tag = params["tag"] self.tag = params["tag"]
self.tags = self.get_tags()
self.search = params["search"] self.search = params["search"]
def get_profile_id(self, profile_name):
profiles = ""
for profile in self._get("qualityProfile" if self.version == "v3" else "profile"):
if len(profiles) > 0:
profiles += ", "
profiles += profile["name"]
if profile["name"] == profile_name:
return profile["id"]
raise Failed(f"Radarr Error: quality_profile: {profile_name} does not exist in radarr. Profiles available: {profiles}")
def get_tags(self):
return {tag["label"]: tag["id"] for tag in self._get("tag")}
def add_tags(self, tags):
added = False
for label in tags:
if str(label).lower() not in self.tags:
added = True
self._post("tag", {"label": str(label).lower()})
if added:
self.tags = self.get_tags()
def lookup(self, tmdb_id):
results = self._get("movie/lookup", params={"term": f"tmdb:{tmdb_id}"})
if results:
return results[0]
else:
raise Failed(f"Sonarr Error: TMDb ID: {tmdb_id} not found")
def add_tmdb(self, tmdb_ids, **options): def add_tmdb(self, tmdb_ids, **options):
logger.info("") logger.info("")
util.separator(f"Adding to Radarr", space=False, border=False) util.separator(f"Adding to Radarr", space=False, border=False)
logger.info("") logger.info("")
logger.debug(f"TMDb IDs: {tmdb_ids}") logger.debug(f"TMDb IDs: {tmdb_ids}")
tag_nums = [] logger.debug("")
add_count = 0
folder = options["folder"] if "folder" in options else self.root_folder_path folder = options["folder"] if "folder" in options else self.root_folder_path
monitor = options["monitor"] if "monitor" in options else self.monitor monitor = options["monitor"] if "monitor" in options else self.monitor
availability = options["availability"] if "availability" in options else self.availability availability = availability_translation[options["availability"] if "availability" in options else self.availability]
quality_profile_id = self.get_profile_id(options["quality"]) if "quality" in options else self.quality_profile_id quality_profile = options["quality"] if "quality" in options else self.quality_profile
tags = options["tag"] if "tag" in options else self.tag tags = options["tag"] if "tag" in options else self.tag
search = options["search"] if "search" in options else self.search search = options["search"] if "search" in options else self.search
if tags: try:
self.add_tags(tags) added, exists, invalid = self.api.add_multiple_movies(tmdb_ids, folder, quality_profile, monitor, search, availability, tags)
tag_nums = [self.tags[label.lower()] for label in tags if label.lower() in self.tags] except Invalid as e:
for tmdb_id in tmdb_ids: raise Failed(f"Radarr Error: {e}")
try:
movie_info = self.lookup(tmdb_id) if len(added) > 0:
except Failed as e: for movie in added:
logger.error(e) logger.info(f"Added to Radarr | {movie.tmdbId:<6} | {movie.title}")
continue logger.info(f"{len(added)} Movie{'s' if len(added) > 1 else ''} added to Radarr")
poster_url = None if len(exists) > 0:
for image in movie_info["images"]: for movie in exists:
if "coverType" in image and image["coverType"] == "poster" and "remoteUrl" in image: logger.info(f"Already in Radarr | {movie.tmdbId:<6} | {movie.title}")
poster_url = image["remoteUrl"] logger.info(f"{len(exists)} Movie{'s' if len(exists) > 1 else ''} already existing in Radarr")
url_json = { for movie in invalid:
"title": movie_info["title"], logger.info(f"Invalid TMDb ID | {movie}")
f"{'qualityProfileId' if self.version == 'v3' else 'profileId'}": quality_profile_id,
"year": int(movie_info["year"]),
"tmdbid": int(tmdb_id),
"titleslug": movie_info["titleSlug"],
"minimumAvailability": availability_translation[availability],
"monitored": monitor,
"rootFolderPath": folder,
"images": [{"covertype": "poster", "url": poster_url}],
"addOptions": {"searchForMovie": search}
}
if tag_nums:
url_json["tags"] = tag_nums
response = self._post("movie", url_json)
if response.status_code < 400:
logger.info(f"Added to Radarr | {tmdb_id:<6} | {movie_info['title']}")
add_count += 1
else:
try:
logger.error(f"Radarr Error: ({tmdb_id}) {movie_info['title']}: ({response.status_code}) {response.json()[0]['errorMessage']}")
except KeyError:
logger.debug(url_json)
logger.error(f"Radarr Error: {response.json()}")
logger.info(f"{add_count} Movie{'s' if add_count > 1 else ''} added to Radarr")
@retry(stop_max_attempt_number=6, wait_fixed=10000)
def _get(self, url, params=None):
url_params = {"apikey": f"{self.token}"}
if params:
for param in params:
url_params[param] = params[param]
return requests.get(f"{self.base_url}{url}", params=url_params).json()
@retry(stop_max_attempt_number=6, wait_fixed=10000)
def _post(self, url, url_json):
return requests.post(f"{self.base_url}{url}", json=url_json, params={"apikey": f"{self.token}"})

@ -1,8 +1,8 @@
import logging, requests import logging
from json.decoder import JSONDecodeError
from modules import util from modules import util
from modules.util import Failed from modules.util import Failed
from retrying import retry from arrapi import SonarrAPI
from arrapi.exceptions import ArrException, Invalid
logger = logging.getLogger("Plex Meta Manager") logger = logging.getLogger("Plex Meta Manager")
@ -18,149 +18,56 @@ monitor_translation = {
"none": "none" "none": "none"
} }
class SonarrAPI: class Sonarr:
def __init__(self, params, language): def __init__(self, params):
self.url = params["url"] self.url = params["url"]
self.token = params["token"] self.token = params["token"]
self.version = params["version"]
self.base_url = f"{self.url}/api{'/v3/' if self.version == 'v3' else '/'}"
try: try:
result = requests.get(f"{self.base_url}system/status", params={"apikey": f"{self.token}"}).json() self.api = SonarrAPI(self.url, self.token)
except Exception: except ArrException as e:
util.print_stacktrace() raise Failed(e)
raise Failed(f"Sonarr Error: Could not connect to Sonarr at {self.url}")
if "error" in result and result["error"] == "Unauthorized":
raise Failed("Sonarr Error: Invalid API Key")
if "version" not in result:
raise Failed("Sonarr Error: Unexpected Response Check URL")
self.add = params["add"] self.add = params["add"]
self.root_folder_path = params["root_folder_path"] self.root_folder_path = params["root_folder_path"]
self.monitor = params["monitor"] self.monitor = params["monitor"]
self.quality_profile_id = self.get_profile_id(params["quality_profile"], "quality_profile") self.quality_profile = params["quality_profile"]
self.language_profile_id = None self.language_profile_id = None
if self.version == "v3" and params["language_profile"] is not None: self.language_profile = params["language_profile"]
self.language_profile_id = self.get_profile_id(params["language_profile"], "language_profile")
if self.language_profile_id is None:
self.language_profile_id = 1
self.series_type = params["series_type"] self.series_type = params["series_type"]
self.season_folder = params["season_folder"] self.season_folder = params["season_folder"]
self.tag = params["tag"] self.tag = params["tag"]
self.tags = self.get_tags()
self.search = params["search"] self.search = params["search"]
self.cutoff_search = params["cutoff_search"] self.cutoff_search = params["cutoff_search"]
self.language = language
def get_profile_id(self, profile_name, profile_type):
profiles = ""
if profile_type == "quality_profile" and self.version == "v3":
endpoint = "qualityProfile"
elif profile_type == "language_profile":
endpoint = "languageProfile"
else:
endpoint = "profile"
for profile in self._get(endpoint):
if len(profiles) > 0:
profiles += ", "
profiles += profile["name"]
if profile["name"] == profile_name:
return profile["id"]
raise Failed(f"Sonarr Error: {profile_type}: {profile_name} does not exist in sonarr. Profiles available: {profiles}")
def get_tags(self):
return {tag["label"]: tag["id"] for tag in self._get("tag")}
def add_tags(self, tags):
added = False
for label in tags:
if str(label).lower() not in self.tags:
added = True
self._post("tag", {"label": str(label).lower()})
if added:
self.tags = self.get_tags()
def lookup(self, tvdb_id):
results = self._get("series/lookup", params={"term": f"tvdb:{tvdb_id}"})
if results:
return results[0]
else:
raise Failed(f"Sonarr Error: TVDb ID: {tvdb_id} not found")
def add_tvdb(self, tvdb_ids, **options): def add_tvdb(self, tvdb_ids, **options):
logger.info("") logger.info("")
util.separator(f"Adding to Sonarr", space=False, border=False) util.separator(f"Adding to Sonarr", space=False, border=False)
logger.info("") logger.info("")
logger.debug(f"TVDb IDs: {tvdb_ids}") logger.debug(f"TVDb IDs: {tvdb_ids}")
tag_nums = [] logger.debug("")
add_count = 0
folder = options["folder"] if "folder" in options else self.root_folder_path folder = options["folder"] if "folder" in options else self.root_folder_path
monitor = options["monitor"] if "monitor" in options else self.monitor monitor = monitor_translation[options["monitor"] if "monitor" in options else self.monitor]
quality_profile_id = self.get_profile_id(options["quality"], "quality_profile") if "quality" in options else self.quality_profile_id quality_profile = options["quality"] if "quality" in options else self.quality_profile
language_profile_id = self.get_profile_id(options["language"], "language_profile") if "quality" in options else self.language_profile_id language_profile = options["language"] if "language" in options else self.language_profile
language_profile = language_profile if self.api.v3 else 1
series = options["series"] if "series" in options else self.series_type series = options["series"] if "series" in options else self.series_type
season = options["season"] if "season" in options else self.season_folder season = options["season"] if "season" in options else self.season_folder
tags = options["tag"] if "tag" in options else self.tag tags = options["tag"] if "tag" in options else self.tag
search = options["search"] if "search" in options else self.search search = options["search"] if "search" in options else self.search
cutoff_search = options["cutoff_search"] if "cutoff_search" in options else self.cutoff_search cutoff_search = options["cutoff_search"] if "cutoff_search" in options else self.cutoff_search
if tags: try:
self.add_tags(tags) added, exists, invalid = self.api.add_multiple_series(tvdb_ids, folder, quality_profile, language_profile, monitor, season, search, cutoff_search, series, tags)
tag_nums = [self.tags[label.lower()] for label in tags if label.lower() in self.tags] except Invalid as e:
for tvdb_id in tvdb_ids: raise Failed(f"Sonarr Error: {e}")
try:
show_info = self.lookup(tvdb_id)
except Failed as e:
logger.error(e)
continue
poster_url = None
for image in show_info["images"]:
if "coverType" in image and image["coverType"] == "poster" and "remoteUrl" in image:
poster_url = image["remoteUrl"]
url_json = {
"title": show_info["title"],
f"{'qualityProfileId' if self.version == 'v3' else 'profileId'}": quality_profile_id,
"languageProfileId": language_profile_id,
"tvdbId": int(tvdb_id),
"titleslug": show_info["titleSlug"],
"language": self.language,
"monitored": monitor != "none",
"seasonFolder": season,
"seriesType": series,
"rootFolderPath": folder,
"seasons": [],
"images": [{"covertype": "poster", "url": poster_url}],
"addOptions": {
"searchForMissingEpisodes": search,
"searchForCutoffUnmetEpisodes": cutoff_search,
"monitor": monitor_translation[monitor]
}
}
if tag_nums:
url_json["tags"] = tag_nums
response = self._post("series", url_json)
if response.status_code < 400:
logger.info(f"Added to Sonarr | {tvdb_id:<6} | {show_info['title']}")
add_count += 1
else:
try:
logger.error(f"Sonarr Error: ({tvdb_id}) {show_info['title']}: ({response.status_code}) {response.json()[0]['errorMessage']}")
except KeyError:
logger.debug(url_json)
logger.error(f"Sonarr Error: {response.json()}")
except JSONDecodeError:
logger.debug(url_json)
logger.error(f"Sonarr Error: {response}")
logger.info(f"{add_count} Show{'s' if add_count > 1 else ''} added to Sonarr") if len(added) > 0:
for series in added:
logger.info(f"Added to Sonarr | {series.tvdbId:<6} | {series.title}")
logger.info(f"{len(added)} Series added to Sonarr")
@retry(stop_max_attempt_number=6, wait_fixed=10000) if len(exists) > 0:
def _get(self, url, params=None): for series in exists:
url_params = {"apikey": f"{self.token}"} logger.info(f"Already in Sonarr | {series.tvdbId:<6} | {series.title}")
if params: logger.info(f"{len(exists)} Series already existing in Sonarr")
for param in params:
url_params[param] = params[param]
return requests.get(f"{self.base_url}{url}", params=url_params).json()
@retry(stop_max_attempt_number=6, wait_fixed=10000) for series in invalid:
def _post(self, url, url_json): logger.info(f"Invalid TVDb ID | {series}")
return requests.post(f"{self.base_url}{url}", json=url_json, params={"apikey": f"{self.token}"})

@ -3,6 +3,7 @@
PlexAPI==4.5.2 PlexAPI==4.5.2
tmdbv3api==1.7.5 tmdbv3api==1.7.5
trakt.py==4.3.0 trakt.py==4.3.0
arrapi==1.0.0
# More common, flexible # More common, flexible
lxml lxml
requests>=2.4.2 requests>=2.4.2

Loading…
Cancel
Save