commit
82584fabd3
@ -1,108 +1,130 @@
|
|||||||
import logging, re, requests
|
import logging, requests
|
||||||
from modules import util
|
from modules import util
|
||||||
from modules.util import Failed
|
from modules.util import Failed
|
||||||
from retrying import retry
|
from retrying import retry
|
||||||
|
|
||||||
logger = logging.getLogger("Plex Meta Manager")
|
logger = logging.getLogger("Plex Meta Manager")
|
||||||
|
|
||||||
|
availability_translation = {
|
||||||
|
"announced": "announced",
|
||||||
|
"cinemas": "inCinemas",
|
||||||
|
"released": "released",
|
||||||
|
"db": "preDB"
|
||||||
|
}
|
||||||
|
|
||||||
class RadarrAPI:
|
class RadarrAPI:
|
||||||
def __init__(self, tmdb, params):
|
def __init__(self, params):
|
||||||
self.url_params = {"apikey": f"{params['token']}"}
|
self.url = params["url"]
|
||||||
self.base_url = f"{params['url']}/api{'/v3' if params['version'] == 'v3' else ''}/"
|
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=self.url_params).json()
|
result = requests.get(f"{self.base_url}system/status", params={"apikey": f"{self.token}"}).json()
|
||||||
except Exception:
|
except Exception:
|
||||||
util.print_stacktrace()
|
util.print_stacktrace()
|
||||||
raise Failed(f"Radarr Error: Could not connect to Radarr at {params['url']}")
|
raise Failed(f"Radarr Error: Could not connect to Radarr at {self.url}")
|
||||||
if "error" in result and result["error"] == "Unauthorized":
|
if "error" in result and result["error"] == "Unauthorized":
|
||||||
raise Failed("Radarr Error: Invalid API Key")
|
raise Failed("Radarr Error: Invalid API Key")
|
||||||
if "version" not in result:
|
if "version" not in result:
|
||||||
raise Failed("Radarr Error: Unexpected Response Check URL")
|
raise Failed("Radarr Error: Unexpected Response Check URL")
|
||||||
self.quality_profile_id = None
|
self.add = params["add"]
|
||||||
|
self.root_folder_path = params["root_folder_path"]
|
||||||
|
self.monitor = params["monitor"]
|
||||||
|
self.availability = params["availability"]
|
||||||
|
self.quality_profile_id = self.get_profile_id(params["quality_profile"])
|
||||||
|
self.tag = params["tag"]
|
||||||
|
self.tags = self.get_tags()
|
||||||
|
self.search = params["search"]
|
||||||
|
|
||||||
|
def get_profile_id(self, profile_name):
|
||||||
profiles = ""
|
profiles = ""
|
||||||
for profile in self.send_get(f"{self.base_url}{'qualityProfile' if params['version'] == 'v3' else 'profile'}"):
|
for profile in self.send_get("qualityProfile" if self.version == "v3" else "profile"):
|
||||||
if len(profiles) > 0:
|
if len(profiles) > 0:
|
||||||
profiles += ", "
|
profiles += ", "
|
||||||
profiles += profile["name"]
|
profiles += profile["name"]
|
||||||
if profile["name"] == params["quality_profile"]:
|
if profile["name"] == profile_name:
|
||||||
self.quality_profile_id = profile["id"]
|
return profile["id"]
|
||||||
if not self.quality_profile_id:
|
raise Failed(f"Radarr Error: quality_profile: {profile_name} does not exist in radarr. Profiles available: {profiles}")
|
||||||
raise Failed(f"Radarr Error: quality_profile: {params['quality_profile']} does not exist in radarr. Profiles available: {profiles}")
|
|
||||||
self.tmdb = tmdb
|
def get_tags(self):
|
||||||
self.url = params["url"]
|
return {tag["label"]: tag["id"] for tag in self.send_get("tag")}
|
||||||
self.version = params["version"]
|
|
||||||
self.token = params["token"]
|
def add_tags(self, tags):
|
||||||
self.root_folder_path = params["root_folder_path"]
|
added = False
|
||||||
self.add = params["add"]
|
for label in tags:
|
||||||
self.search = params["search"]
|
if label not in self.tags:
|
||||||
self.tag = params["tag"]
|
added = True
|
||||||
|
self.send_post("tag", {"label": str(label)})
|
||||||
|
if added:
|
||||||
|
self.tags = self.get_tags()
|
||||||
|
|
||||||
|
def lookup(self, tmdb_id):
|
||||||
|
results = self.send_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, tag=None):
|
def add_tmdb(self, tmdb_ids, **options):
|
||||||
logger.info("")
|
logger.info("")
|
||||||
logger.debug(f"TMDb IDs: {tmdb_ids}")
|
logger.debug(f"TMDb IDs: {tmdb_ids}")
|
||||||
tag_nums = []
|
tag_nums = []
|
||||||
add_count = 0
|
add_count = 0
|
||||||
if tag is None:
|
folder = options["folder"] if "folder" in options else self.root_folder_path
|
||||||
tag = self.tag
|
monitor = options["monitor"] if "monitor" in options else self.monitor
|
||||||
if tag:
|
availability = options["availability"] if "availability" in options else self.availability
|
||||||
tag_cache = {}
|
quality_profile_id = self.get_profile_id(options["quality"]) if "quality" in options else self.quality_profile_id
|
||||||
for label in tag:
|
tags = options["tag"] if "tag" in options else self.tag
|
||||||
self.send_post(f"{self.base_url}tag", {"label": str(label)})
|
search = options["search"] if "search" in options else self.search
|
||||||
for t in self.send_get(f"{self.base_url}tag"):
|
if tags:
|
||||||
tag_cache[t["label"]] = t["id"]
|
self.add_tags(tags)
|
||||||
for label in tag:
|
tag_nums = [self.tags[label] for label in tags if label in self.tags]
|
||||||
if label in tag_cache:
|
|
||||||
tag_nums.append(tag_cache[label])
|
|
||||||
for tmdb_id in tmdb_ids:
|
for tmdb_id in tmdb_ids:
|
||||||
try:
|
try:
|
||||||
movie = self.tmdb.get_movie(tmdb_id)
|
movie_info = self.lookup(tmdb_id)
|
||||||
except Failed as e:
|
except Failed as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
poster_url = None
|
||||||
year = movie.release_date.split("-")[0]
|
for image in movie_info["images"]:
|
||||||
except AttributeError:
|
if "coverType" in image and image["coverType"] == "poster" and "remoteUrl" in image:
|
||||||
logger.error(f"TMDb Error: No year for ({tmdb_id}) {movie.title}")
|
poster_url = image["remoteUrl"]
|
||||||
continue
|
|
||||||
|
|
||||||
if year.isdigit() is False:
|
|
||||||
logger.error(f"TMDb Error: No release date yet for ({tmdb_id}) {movie.title}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
poster = f"https://image.tmdb.org/t/p/original{movie.poster_path}"
|
|
||||||
|
|
||||||
titleslug = re.sub(r"([^\s\w]|_)+", "", f"{movie.title} {year}").replace(" ", "-").lower()
|
|
||||||
|
|
||||||
url_json = {
|
url_json = {
|
||||||
"title": movie.title,
|
"title": movie_info["title"],
|
||||||
f"{'qualityProfileId' if self.version == 'v3' else 'profileId'}": self.quality_profile_id,
|
f"{'qualityProfileId' if self.version == 'v3' else 'profileId'}": quality_profile_id,
|
||||||
"year": int(year),
|
"year": int(movie_info["year"]),
|
||||||
"tmdbid": int(tmdb_id),
|
"tmdbid": int(tmdb_id),
|
||||||
"titleslug": titleslug,
|
"titleslug": movie_info["titleSlug"],
|
||||||
"monitored": True,
|
"minimumAvailability": availability_translation[availability],
|
||||||
"rootFolderPath": self.root_folder_path,
|
"monitored": monitor,
|
||||||
"images": [{"covertype": "poster", "url": poster}],
|
"rootFolderPath": folder,
|
||||||
"addOptions": {"searchForMovie": self.search}
|
"images": [{"covertype": "poster", "url": poster_url}],
|
||||||
|
"addOptions": {"searchForMovie": search}
|
||||||
}
|
}
|
||||||
if tag_nums:
|
if tag_nums:
|
||||||
url_json["tags"] = tag_nums
|
url_json["tags"] = tag_nums
|
||||||
response = self.send_post(f"{self.base_url}movie", url_json)
|
response = self.send_post("movie", url_json)
|
||||||
if response.status_code < 400:
|
if response.status_code < 400:
|
||||||
logger.info(f"Added to Radarr | {tmdb_id:<6} | {movie.title}")
|
logger.info(f"Added to Radarr | {tmdb_id:<6} | {movie_info['title']}")
|
||||||
add_count += 1
|
add_count += 1
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
logger.error(f"Radarr Error: ({tmdb_id}) {movie.title}: ({response.status_code}) {response.json()[0]['errorMessage']}")
|
logger.error(f"Radarr Error: ({tmdb_id}) {movie_info['title']}: ({response.status_code}) {response.json()[0]['errorMessage']}")
|
||||||
except KeyError:
|
except KeyError:
|
||||||
logger.debug(url_json)
|
logger.debug(url_json)
|
||||||
logger.error(f"Radarr Error: {response.json()}")
|
logger.error(f"Radarr Error: {response.json()}")
|
||||||
logger.info(f"{add_count} Movie{'s' if add_count > 1 else ''} added to Radarr")
|
logger.info(f"{add_count} Movie{'s' if add_count > 1 else ''} added to Radarr")
|
||||||
|
|
||||||
@retry(stop_max_attempt_number=6, wait_fixed=10000)
|
@retry(stop_max_attempt_number=6, wait_fixed=10000)
|
||||||
def send_get(self, url):
|
def send_get(self, url, params=None):
|
||||||
return requests.get(url, params=self.url_params).json()
|
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)
|
@retry(stop_max_attempt_number=6, wait_fixed=10000)
|
||||||
def send_post(self, url, url_json):
|
def send_post(self, url, url_json):
|
||||||
return requests.post(url, json=url_json, params=self.url_params)
|
return requests.post(f"{self.base_url}{url}", json=url_json, params={"apikey": f"{self.token}"})
|
||||||
|
@ -1,101 +1,164 @@
|
|||||||
import logging, re, requests
|
import logging, requests
|
||||||
|
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 retrying import retry
|
||||||
|
|
||||||
logger = logging.getLogger("Plex Meta Manager")
|
logger = logging.getLogger("Plex Meta Manager")
|
||||||
|
|
||||||
|
series_type = ["standard", "daily", "anime"]
|
||||||
|
monitor_translation = {
|
||||||
|
"all": "all",
|
||||||
|
"future": "future",
|
||||||
|
"missing": "missing",
|
||||||
|
"existing": "existing",
|
||||||
|
"pilot": "pilot",
|
||||||
|
"first": "firstSeason",
|
||||||
|
"latest": "latestSeason",
|
||||||
|
"none": "none"
|
||||||
|
}
|
||||||
|
|
||||||
class SonarrAPI:
|
class SonarrAPI:
|
||||||
def __init__(self, tvdb, params, language):
|
def __init__(self, params, language):
|
||||||
self.url_params = {"apikey": f"{params['token']}"}
|
self.url = params["url"]
|
||||||
self.base_url = f"{params['url']}/api{'/v3/' if params['version'] == 'v3' else '/'}"
|
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=self.url_params).json()
|
result = requests.get(f"{self.base_url}system/status", params={"apikey": f"{self.token}"}).json()
|
||||||
except Exception:
|
except Exception:
|
||||||
util.print_stacktrace()
|
util.print_stacktrace()
|
||||||
raise Failed(f"Sonarr Error: Could not connect to Sonarr at {params['url']}")
|
raise Failed(f"Sonarr Error: Could not connect to Sonarr at {self.url}")
|
||||||
if "error" in result and result["error"] == "Unauthorized":
|
if "error" in result and result["error"] == "Unauthorized":
|
||||||
raise Failed("Sonarr Error: Invalid API Key")
|
raise Failed("Sonarr Error: Invalid API Key")
|
||||||
if "version" not in result:
|
if "version" not in result:
|
||||||
raise Failed("Sonarr Error: Unexpected Response Check URL")
|
raise Failed("Sonarr Error: Unexpected Response Check URL")
|
||||||
self.quality_profile_id = None
|
self.add = params["add"]
|
||||||
|
self.root_folder_path = params["root_folder_path"]
|
||||||
|
self.monitor = params["monitor"]
|
||||||
|
self.quality_profile_id = self.get_profile_id(params["quality_profile"], "quality_profile")
|
||||||
|
self.language_profile_id = None
|
||||||
|
if self.version == "v3" and params["language_profile"] is not None:
|
||||||
|
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.season_folder = params["season_folder"]
|
||||||
|
self.tag = params["tag"]
|
||||||
|
self.tags = self.get_tags()
|
||||||
|
self.search = params["search"]
|
||||||
|
self.cutoff_search = params["cutoff_search"]
|
||||||
|
self.language = language
|
||||||
|
|
||||||
|
def get_profile_id(self, profile_name, profile_type):
|
||||||
profiles = ""
|
profiles = ""
|
||||||
for profile in self.send_get(f"{self.base_url}{'qualityProfile' if params['version'] == 'v3' else 'profile'}"):
|
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.send_get(endpoint):
|
||||||
if len(profiles) > 0:
|
if len(profiles) > 0:
|
||||||
profiles += ", "
|
profiles += ", "
|
||||||
profiles += profile["name"]
|
profiles += profile["name"]
|
||||||
if profile["name"] == params["quality_profile"]:
|
if profile["name"] == profile_name:
|
||||||
self.quality_profile_id = profile["id"]
|
return profile["id"]
|
||||||
if not self.quality_profile_id:
|
raise Failed(f"Sonarr Error: {profile_type}: {profile_name} does not exist in sonarr. Profiles available: {profiles}")
|
||||||
raise Failed(f"Sonarr Error: quality_profile: {params['quality_profile']} does not exist in sonarr. Profiles available: {profiles}")
|
|
||||||
self.tvdb = tvdb
|
|
||||||
self.language = language
|
|
||||||
self.url = params["url"]
|
|
||||||
self.version = params["version"]
|
|
||||||
self.token = params["token"]
|
|
||||||
self.root_folder_path = params["root_folder_path"]
|
|
||||||
self.add = params["add"]
|
|
||||||
self.search = params["search"]
|
|
||||||
self.season_folder = params["season_folder"]
|
|
||||||
self.tag = params["tag"]
|
|
||||||
|
|
||||||
def add_tvdb(self, tvdb_ids, tag=None):
|
def get_tags(self):
|
||||||
|
return {tag["label"]: tag["id"] for tag in self.send_get("tag")}
|
||||||
|
|
||||||
|
def add_tags(self, tags):
|
||||||
|
added = False
|
||||||
|
for label in tags:
|
||||||
|
if label not in self.tags:
|
||||||
|
added = True
|
||||||
|
self.send_post("tag", {"label": str(label)})
|
||||||
|
if added:
|
||||||
|
self.tags = self.get_tags()
|
||||||
|
|
||||||
|
def lookup(self, tvdb_id):
|
||||||
|
results = self.send_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):
|
||||||
logger.info("")
|
logger.info("")
|
||||||
logger.debug(f"TVDb IDs: {tvdb_ids}")
|
logger.debug(f"TVDb IDs: {tvdb_ids}")
|
||||||
tag_nums = []
|
tag_nums = []
|
||||||
add_count = 0
|
add_count = 0
|
||||||
if tag is None:
|
folder = options["folder"] if "folder" in options else self.root_folder_path
|
||||||
tag = self.tag
|
monitor = options["monitor"] if "monitor" in options else self.monitor
|
||||||
if tag:
|
quality_profile_id = self.get_profile_id(options["quality"], "quality_profile") if "quality" in options else self.quality_profile_id
|
||||||
tag_cache = {}
|
language_profile_id = self.get_profile_id(options["language"], "language_profile") if "quality" in options else self.quality_profile_id
|
||||||
for label in tag:
|
series = options["series"] if "series" in options else self.series_type
|
||||||
self.send_post(f"{self.base_url}tag", {"label": str(label)})
|
season = options["season"] if "season" in options else self.season_folder
|
||||||
for t in self.send_get(f"{self.base_url}tag"):
|
tags = options["tag"] if "tag" in options else self.tag
|
||||||
tag_cache[t["label"]] = t["id"]
|
search = options["search"] if "search" in options else self.search
|
||||||
for label in tag:
|
cutoff_search = options["cutoff_search"] if "cutoff_search" in options else self.cutoff_search
|
||||||
if label in tag_cache:
|
if tags:
|
||||||
tag_nums.append(tag_cache[label])
|
self.add_tags(tags)
|
||||||
|
tag_nums = [self.tags[label] for label in tags if label in self.tags]
|
||||||
for tvdb_id in tvdb_ids:
|
for tvdb_id in tvdb_ids:
|
||||||
try:
|
try:
|
||||||
show = self.tvdb.get_series(self.language, tvdb_id)
|
show_info = self.lookup(tvdb_id)
|
||||||
except Failed as e:
|
except Failed as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
titleslug = re.sub(r"([^\s\w]|_)+", "", show.title).replace(" ", "-").lower()
|
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 = {
|
url_json = {
|
||||||
"title": show.title,
|
"title": show_info["title"],
|
||||||
f"{'qualityProfileId' if self.version == 'v3' else 'profileId'}": self.quality_profile_id,
|
f"{'qualityProfileId' if self.version == 'v3' else 'profileId'}": quality_profile_id,
|
||||||
"languageProfileId": 1,
|
"languageProfileId": language_profile_id,
|
||||||
"tvdbId": int(tvdb_id),
|
"tvdbId": int(tvdb_id),
|
||||||
"titleslug": titleslug,
|
"titleslug": show_info["titleSlug"],
|
||||||
"language": self.language,
|
"language": self.language,
|
||||||
"monitored": True,
|
"monitored": monitor != "none",
|
||||||
"seasonFolder": self.season_folder,
|
"seasonFolder": season,
|
||||||
"rootFolderPath": self.root_folder_path,
|
"seriesType": series,
|
||||||
|
"rootFolderPath": folder,
|
||||||
"seasons": [],
|
"seasons": [],
|
||||||
"images": [{"covertype": "poster", "url": show.poster_path}],
|
"images": [{"covertype": "poster", "url": poster_url}],
|
||||||
"addOptions": {"searchForMissingEpisodes": self.search}
|
"addOptions": {
|
||||||
|
"searchForMissingEpisodes": search,
|
||||||
|
"searchForCutoffUnmetEpisodes": cutoff_search,
|
||||||
|
"monitor": monitor_translation[monitor]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if tag_nums:
|
if tag_nums:
|
||||||
url_json["tags"] = tag_nums
|
url_json["tags"] = tag_nums
|
||||||
response = self.send_post(f"{self.base_url}series", url_json)
|
response = self.send_post("series", url_json)
|
||||||
if response.status_code < 400:
|
if response.status_code < 400:
|
||||||
logger.info(f"Added to Sonarr | {tvdb_id:<6} | {show.title}")
|
logger.info(f"Added to Sonarr | {tvdb_id:<6} | {show_info['title']}")
|
||||||
add_count += 1
|
add_count += 1
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
logger.error(f"Sonarr Error: ({tvdb_id}) {show.title}: ({response.status_code}) {response.json()[0]['errorMessage']}")
|
logger.error(f"Sonarr Error: ({tvdb_id}) {show_info['title']}: ({response.status_code}) {response.json()[0]['errorMessage']}")
|
||||||
except KeyError:
|
except KeyError:
|
||||||
logger.debug(url_json)
|
logger.debug(url_json)
|
||||||
logger.error(f"Sonarr Error: {response.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")
|
logger.info(f"{add_count} Show{'s' if add_count > 1 else ''} added to Sonarr")
|
||||||
|
|
||||||
@retry(stop_max_attempt_number=6, wait_fixed=10000)
|
@retry(stop_max_attempt_number=6, wait_fixed=10000)
|
||||||
def send_get(self, url):
|
def send_get(self, url, params=None):
|
||||||
return requests.get(url, params=self.url_params).json()
|
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)
|
@retry(stop_max_attempt_number=6, wait_fixed=10000)
|
||||||
def send_post(self, url, url_json):
|
def send_post(self, url, url_json):
|
||||||
return requests.post(url, json=url_json, params=self.url_params)
|
return requests.post(f"{self.base_url}{url}", json=url_json, params={"apikey": f"{self.token}"})
|
||||||
|
Loading…
Reference in new issue