From 2a7171dc68184573ccc8247bbcbdbf8b52f18db3 Mon Sep 17 00:00:00 2001 From: seriox Date: Tue, 16 Aug 2022 01:17:04 +0200 Subject: [PATCH 01/16] test --- searcharr.py | 30 ++++++++++++++++++++++++++++++ settings-sample.py | 1 + sonarr.py | 16 +++++++++++++++- 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/searcharr.py b/searcharr.py index 5834876..71328fe 100644 --- a/searcharr.py +++ b/searcharr.py @@ -96,6 +96,35 @@ def __init__(self, token): ) self.sonarr._quality_profiles = quality_profiles + language_profiles = [] + if not isinstance(settings.sonarr_language_profile_id, list): + settings.sonarr_language_profile_id = [ + settings.sonarr_language_profile_id + ] + for i in settings.sonarr_language_profile_id: + logger.debug( + f"Looking up/validating Sonarr language profile id for [{i}]..." + ) + foundProfile = self.sonarr.lookup_language_profile(i) + if not foundProfile: + logger.error(f"Sonarr language profile id/name [{i}] is invalid!") + else: + logger.debug( + f"Found Sonarr language profile for [{i}]: [{foundProfile}]" + ) + language_profiles.append(foundProfile) + if not len(language_profiles): + logger.warning( + f"No valid Sonarr language profile(s) provided! Using all of the language profiles I found in Sonarr: {self.sonarr._language_profiles}" + ) + else: + logger.debug( + f"Using the following Sonarr language profile(s): {[(x['id'], x['name']) for x in language_profiles]}" + ) + self.sonarr._language_profiles = language_profiles + + + root_folders = [] if not hasattr(settings, "sonarr_series_paths"): settings.sonarr_series_paths = [] @@ -1149,6 +1178,7 @@ def _prepare_response( add=False, paths=None, quality_profiles=None, + language_profiles=None monitor_options=None, tags=None, ): diff --git a/settings-sample.py b/settings-sample.py index 5efa933..3b968c9 100644 --- a/settings-sample.py +++ b/settings-sample.py @@ -21,6 +21,7 @@ sonarr_url = "" # http://192.168.0.100:8989 sonarr_api_key = "" sonarr_quality_profile_id = ["HD - 720p/1080p"] # can be name or id value - include multiple to allow the user to choose +sonarr_language_profile_id = ["English", "German"] sonarr_add_monitored = True sonarr_search_on_add = True sonarr_tag_with_username = True diff --git a/sonarr.py b/sonarr.py index 909d245..26f0af8 100644 --- a/sonarr.py +++ b/sonarr.py @@ -24,10 +24,12 @@ def __init__(self, api_url, api_key, verbose=False): ) self.api_url = api_url + "/api/{endpoint}?apikey=" + api_key self._quality_profiles = self.get_all_quality_profiles() + self._language_profiles = self.get_all_language_profiles() self._root_folders = self.get_root_folders() self._all_series = {} self.get_all_series() - + + def lookup_series(self, title=None, tvdb_id=None): r = self._api_get( "series/lookup", {"term": f"tvdb:{tvdb_id}" if tvdb_id else quote(title)} @@ -100,6 +102,7 @@ def add_series( path = additional_data["p"] quality = int(additional_data["q"]) + language = int(additional_data["l"]) monitor_options = int(additional_data.get("m", 0)) if monitor_options == 1: # Monitor only the first season @@ -130,6 +133,7 @@ def add_series( "tvdbId": series_info["tvdbId"], "title": series_info["title"], "qualityProfileId": quality, + "languageProfileId": language, "titleSlug": series_info["titleSlug"], "images": series_info["images"], "seasons": series_info["seasons"], @@ -226,9 +230,19 @@ def lookup_quality_profile(self, v): None, ) + def lookup_language_profile(self, v): + # Look up language profile from a profile name or id + return next( + (x for x in self._language_profiles if str(v) in [x["name"], str(x["id"])]), + None, + ) + def get_all_quality_profiles(self): return self._api_get("profile", {}) or None + def get_all_language_profiles(self): + return self._api_get("language", {}) or None + def lookup_root_folder(self, v): # Look up root folder from a path or id return next( From e6304f7bb7e04b3330d0fd68198410af12e5902c Mon Sep 17 00:00:00 2001 From: seriox Date: Tue, 16 Aug 2022 02:07:17 +0200 Subject: [PATCH 02/16] Multilanguage Support added --- .vs/searcharr-languageProfile/v16/.suo | Bin 0 -> 3584 bytes .vs/slnx.sqlite | Bin 0 -> 90112 bytes lang/en-us.yml | 2 + searcharr.py | 74 ++++++++++++++++++++++++- settings-sample.py | 2 +- sonarr.py | 2 +- 6 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 .vs/searcharr-languageProfile/v16/.suo create mode 100644 .vs/slnx.sqlite diff --git a/.vs/searcharr-languageProfile/v16/.suo b/.vs/searcharr-languageProfile/v16/.suo new file mode 100644 index 0000000000000000000000000000000000000000..b651f0ba26abd36c3383279b02d32c4d1b8dba02 GIT binary patch literal 3584 zcmca`Uhu)fjZzO8(10BSGsD0CoD6J8;*3Bx6O1pwz`z1zgT(&*|NkE(%nZap<|r6K zA@CRIR#5znf{_^lK@9moSi+#d;L4B(q>C6TL0Oq-AjZIolIMxh2rDm$QA>;(^+C)(5aq8U8dV8-nra)G5?3b6c3WGDu*iWthkBuG3HD3T0RkpdJc1JWS*M4*afh72%W#Gnf-Jrltt zXDN_Q2g*ar^faK}93T&(rvzA{Ldw@-ppIgo%3Ov5u)H3Su4Jg)5!w0f^&)re*$oS? zBpi5VSCYu9|4RAb;s}*Ty3;c=LFF2{+d*QT_SKvD CSzy2b literal 0 HcmV?d00001 diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..b5279996e9e60d3d670201915244995715b784c9 GIT binary patch literal 90112 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 literal 0 HcmV?d00001 diff --git a/lang/en-us.yml b/lang/en-us.yml index 02b269c..ddd0633 100644 --- a/lang/en-us.yml +++ b/lang/en-us.yml @@ -25,6 +25,7 @@ convo_not_found: I received your command, but I don't recognize the conversation search_canceled: Search canceled! no_root_folders: "Error adding {kind}: no root folders enabled for {app}! Please check your Searcharr configuration and try again." no_quality_profiles: "Error adding {kind}: no quality profiles enabled for {app}! Please check your Searcharr configuration and try again." +no_language_profiles: "Error adding {kind}: no language profiles enabled for {app}! Please check your Searcharr configuration and try again." all_seasons: All Seasons first_season: First Season Only latest_season: Latest Season Only @@ -42,6 +43,7 @@ add_tag_button: "Add Tag: {tag}" finished_tagging_button: Finished Tagging monitor_button: Monitor {option} add_quality_button: "Add Quality: {quality}" +select_language_button: "Select Language: {language}" add_path_button: Add to {path} add_button: Add {kind}! already_added_button: Already Added! diff --git a/searcharr.py b/searcharr.py index 71328fe..2de0c45 100644 --- a/searcharr.py +++ b/searcharr.py @@ -853,6 +853,68 @@ def callback(self, update, context): query.answer() return + if not additional_data.get("l"): + language_profiles = ( + self.sonarr._language_profiles + if convo["type"] == "series" + else self.radarr._language_profiles + ) + if len(language_profiles) > 1: + # prepare response to prompt user to select language profile, and return + reply_message, reply_markup = self._prepare_response( + convo["type"], + r, + cid, + i, + len(convo["results"]), + add=True, + language_profiles=language_profiles, + ) + try: + query.message.edit_media( + media=InputMediaPhoto(r["remotePoster"]), + reply_markup=reply_markup, + ) + except BadRequest as e: + if str(e) in self._bad_request_poster_error_messages: + logger.error( + f"Error sending photo [{r['remotePoster']}]: BadRequest: {e}. Attempting to send with default poster..." + ) + query.message.edit_media( + media=InputMediaPhoto( + "https://artworks.thetvdb.com/banners/images/missing/movie.jpg" + ), + reply_markup=reply_markup, + ) + else: + raise + query.bot.edit_message_caption( + chat_id=query.message.chat_id, + message_id=query.message.message_id, + caption=reply_message, + reply_markup=reply_markup, + ) + query.answer() + return + elif len(language_profiles) == 1: + logger.debug( + f"Only one language profile enabled. Adding/Updating additional data for cid=[{cid}], key=[q], value=[{language_profiles[0]['id']}]..." + ) + self._update_add_data(cid, "l", language_profiles[0]["id"]) + else: + self._delete_conversation(cid) + query.message.reply_text( + self._xlate( + "no_language_profiles", + kind=self._xlate(convo["type"]), + app="Sonarr" if convo["type"] == "series" else "Radarr", + ) + ) + query.message.delete() + query.answer() + return + + if ( convo["type"] == "series" and settings.sonarr_season_monitor_prompt @@ -1178,7 +1240,7 @@ def _prepare_response( add=False, paths=None, quality_profiles=None, - language_profiles=None + language_profiles=None, monitor_options=None, tags=None, ): @@ -1255,6 +1317,16 @@ def _prepare_response( ) ], ) + elif language_profiles: + for l in language_profiles: + keyboard.append( + [ + InlineKeyboardButton( + self._xlate("select_language_button", language=l["name"]), + callback_data=f"{cid}^^^{i}^^^add^^l={l['id']}", + ) + ], + ) elif paths: for p in paths: keyboard.append( diff --git a/settings-sample.py b/settings-sample.py index 3b968c9..e67027a 100644 --- a/settings-sample.py +++ b/settings-sample.py @@ -21,7 +21,7 @@ sonarr_url = "" # http://192.168.0.100:8989 sonarr_api_key = "" sonarr_quality_profile_id = ["HD - 720p/1080p"] # can be name or id value - include multiple to allow the user to choose -sonarr_language_profile_id = ["English", "German"] +sonarr_language_profile_id = ["English", "German"] # select languages, include multiple to allow the user to choose sonarr_add_monitored = True sonarr_search_on_add = True sonarr_tag_with_username = True diff --git a/sonarr.py b/sonarr.py index 26f0af8..8017b85 100644 --- a/sonarr.py +++ b/sonarr.py @@ -241,7 +241,7 @@ def get_all_quality_profiles(self): return self._api_get("profile", {}) or None def get_all_language_profiles(self): - return self._api_get("language", {}) or None + return self._api_get("v3/languageprofile", {}) or None def lookup_root_folder(self, v): # Look up root folder from a path or id From 4b565af6dbe1b9685a5051c6ddfac78f4449fafd Mon Sep 17 00:00:00 2001 From: StarvingDeveloper Date: Wed, 5 Oct 2022 14:39:21 +0300 Subject: [PATCH 03/16] Create lt-lt.yml Lithuanian translation --- lang/lt-lt.yml | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 lang/lt-lt.yml diff --git a/lang/lt-lt.yml b/lang/lt-lt.yml new file mode 100644 index 0000000..0a74bd5 --- /dev/null +++ b/lang/lt-lt.yml @@ -0,0 +1,59 @@ +language_ietf: lt-lt +language_label: Lietuvių + +movie: filmai +series: serialai +season: sezonas +title: pavadinimas +title_here: pavadinimas čia +password: slaptažodis +admin_password: administratoriaus slaptažodis +admin_auth_success: Administratoriaus autorizacija sėkminga. Naudokite {commands} komandų sąrašui. +already_authenticated: Jūs jau autorizavotės. Pabandykit {commands} kad sužinotumėte komandas. +auth_successful: Autorizacija sėkminga. Naudokite {commands} komandų sąrašui. +incorrect_pw: Neteisingas slaptažodis. +auth_required: Jūs man nepažįstamas... Prašome autorizuotis su {commands} komanda. +admin_auth_required: Jūs neturite administratoriaus teisių... Prašome autorizuotis su {commands} komanda. +radarr_disabled: Atsiprašau, bet negalima ieškoti filmų. +include_movie_title_in_cmd: Prašome kartu su komanda parašyti filmo pavadinimą, pvz. {commands} +no_matching_movies: Atsiprašau, bet toks filmas nerastas. +sonarr_disabled: Atsiprašau, bet negalima ieškoti serialų. +include_series_title_in_cmd: Prašome kartu su komanda parašyti serialo pavadinimą, pvz. {commands} +no_matching_series: Atsiprašau, bet toks serialas nerastas. +no_users_found: Atsiprašau, bet vartotojas nerastas. Kreipkitės į administratorių... +convo_not_found: Aš gavau komandą, bet negaliu jos įvykdyti. Prašau pabandykit iš naujo. +search_canceled: Paieška atšaukta! +no_root_folders: "Klaida {kind}: neaprašyta {app} šakninis aplankas! Patikrinkite Searcharr konfigūraciją ir bandykite dar kartą." +no_quality_profiles: "Klaida {kind}: neaprašyti {app} kokybės profiliai! Patikrinkite Searcharr konfigūraciją ir bandykite dar kartą." +all_seasons: Visus sezonus +first_season: Tik pirmą sezoną +latest_season: Tik paskutinį sezoną +added: {title} sėkmingai įtrauktas! +unknown_error_adding: Bandant įtraukti įvyko nežinoma klaida {kind}! +removed_user: Vartotojas [{user}] sėkmingai pašalintas! +unknown_error_removing_user: Bandant pašalinti [{user}] įvyko nežinoma klaida! +added_admin_access: Vartotojui [{user}] suteiktos administratoriaus teisės! +unknown_error_adding_admin: Įvyko nežinoma klaida bandant vartotojui [{user}] suteikti administratoriaus teises! +removed_admin_access: Vartotojo [{user}] administratoriaus teisės sėkmingai pašalintos! +unknown_error_removing_admin: bandant pašalinti vartotojo [{user}] administratoriaus teises įvyko nežinoma klaida! +prev_button: < Ank. +next_button: Sek. > +add_tag_button: "Pridėti žymą: {tag}" +finished_tagging_button: Žymėjimas baigtas +monitor_button: Stebėti {option} +add_quality_button: "Pridėti kokybę: {quality}" +add_path_button: Įtraukti į {path} +add_button: Pridėti {kind}! +already_added_button: Pridėta! +cancel_search_button: Atšaukti paiešką +add_series_anime_button: Pridėti serialą kaip anime! +unexpected_error: Kažkas nutiko! +remove_user_button: Pašalinti +make_admin_button: Sukurti administratorių +remove_admin_button: Pašalinti administratorių +done: Atlikta +listing_users_pagination: Searcharr vartotojų sąrašas {page_info}. +help_sonarr: Naudokite {series_commands} norėdami įtraukti serialą į Sonarr. +help_radarr: naudokite {movie_commands} norėdami įtraukti filmą į Radarr. +no_features: Atsiprašome, bet šiuo metu visos funkcijos išjungtos. +admin_help: Kadangi esate administratorius, galite administruoti vartotojus komandų {commands} pagalba. From 8d51b6a7c671c3d4c5b57fb71b35e680b6bc5eb6 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 11 Oct 2022 12:22:20 -0400 Subject: [PATCH 04/16] feat(books): add Readarr support * Support Readarr bot using `/book` command * Add en-us translations * Update docs * Use metadata profile --- README.md | 7 +- lang/en-us.yml | 7 + log.py | 2 +- radarr.py | 2 +- readarr.py | 257 +++++++++++++++++++++++++++++++++++ requirements.txt | 1 + searcharr.py | 331 +++++++++++++++++++++++++++++++++++++++++++-- settings-sample.py | 17 ++- sonarr.py | 2 +- 9 files changed, 606 insertions(+), 20 deletions(-) create mode 100644 readarr.py diff --git a/README.md b/README.md index 5d3e159..872f294 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ### By Todd Roberts https://github.com/toddrob99/searcharr -This bot allows users to add movies to Radarr and series to Sonarr via Telegram messaging app. +This bot allows users to add movies to Radarr, series to Sonarr, and books to Readarr via Telegram messaging app. ## Setup & Run @@ -20,6 +20,7 @@ You are required to update the following settings, at minimum: * Telegram Bot > Token (see [Telegram Bot Setup Instructions](https://core.telegram.org/bots#6-botfather)) * Sonarr > URL, API Key, Quality Profile ID * Radarr > URL, API Key, Quality Profile ID +* Readarr > URL, API Key, Quality Profile ID, Metadata Profile ID ### Docker & Docker-Compose @@ -45,9 +46,9 @@ Send a private message to your bot saying `/start ` where `` **Double Caution**: Do not authenticate as an admin in a group chat. Always use a private message with your bot. -### Search & Add a Series to Sonarr or a Movie to Radarr +### Search & Add a Series to Sonarr, a Movie to Radarr, or a Book to Readarr -Send the bot a (private or group) message saying `/series ` or `/movie <title>` (replace with custom command aliases, as configured in `settings.py`). The bot will reply with information about the first result, along with buttons to move forward and back within the search results, pop out to tvdb, TMDB, or IMDb, add the current series/movie to Sonarr/Radarr, or cancel the search. When you click the button to add the series/movie to Sonarr/Radarr, the bot will ask what root folder to put the series/movie in, then what quality profile to use--unless you have only one root folder or quality profile enabled in Searcharr settings, in which case it will skip those steps and add the series/movie straight away. +Send the bot a (private or group) message saying `/series <title>`, `/movie <title>`, or `/book <title>` (replace with custom command aliases, as configured in `settings.py`). The bot will reply with information about the first result, along with buttons to move forward and back within the search results, pop out to tvdb, TMDB, or IMDb, or Goodreads for books, add the current series/movie/book to Sonarr/Radarr/Readarr, or cancel the search. When you click the button to add the series/movie/book to Sonarr/Radarr/Readarr, the bot will ask what root folder to put the series/movie/book in, then what quality profile to use--unless you have only one root folder or quality profile enabled in Searcharr settings, in which case it will skip those steps and add the series/movie straight away. ### Manage Users diff --git a/lang/en-us.yml b/lang/en-us.yml index 02b269c..998aca9 100644 --- a/lang/en-us.yml +++ b/lang/en-us.yml @@ -4,6 +4,7 @@ language_label: English movie: movie series: series season: season +book: book title: title title_here: title here password: password @@ -57,3 +58,9 @@ help_sonarr: Use {series_commands} to add a series to Sonarr. help_radarr: Use {movie_commands} to add a movie to Radarr. no_features: Sorry, but all of my features are currently disabled. admin_help: Since you are an admin, you can also use {commands} to manage users. +readarr_disabled: Sorry, but book support is disabled. +include_book_title_in_cmd: Please include the book title in the command, e.g. {commands} +no_matching_books: Sorry, but I didn't find any matching books. +help_readarr: Use {book_commands} to add a book to Readarr. +no_metadata_profiles: "Error adding {kind}: no metadata profiles enabled for {app}! Please check your Searcharr configuration and try again." +add_metadata_button: "Add Metadata: {metadata}" \ No newline at end of file diff --git a/log.py b/log.py index bdabcc9..0f14ae1 100644 --- a/log.py +++ b/log.py @@ -1,6 +1,6 @@ """ Searcharr -Sonarr & Radarr Telegram Bot +Sonarr, Radarr & Readarr Telegram Bot Log Helper By Todd Roberts https://github.com/toddrob99/searcharr diff --git a/radarr.py b/radarr.py index c1c0431..35df10c 100644 --- a/radarr.py +++ b/radarr.py @@ -1,6 +1,6 @@ """ Searcharr -Sonarr & Radarr Telegram Bot +Sonarr, Radarr & Readarr Telegram Bot Radarr API Wrapper By Todd Roberts https://github.com/toddrob99/searcharr diff --git a/readarr.py b/readarr.py new file mode 100644 index 0000000..040726d --- /dev/null +++ b/readarr.py @@ -0,0 +1,257 @@ +""" +Searcharr +Sonarr, Radarr & Readarr Telegram Bot +Readarr API Wrapper +By Ayman Bagabas +https://github.com/toddrob99/searcharr +""" +import requests +from urllib.parse import quote + +from log import set_up_logger + + +class Readarr(object): + def __init__(self, api_url, api_key, verbose=False): + self.logger = set_up_logger("searcharr.readarr", verbose, False) + self.logger.debug("Logging started!") + if api_url[-1] == "/": + api_url = api_url[:-1] + if api_url[:4] != "http": + self.logger.error( + "Invalid Readarr URL detected. Please update your settings to include http:// or https:// on the beginning of the URL." + ) + self.readarr_version = self.discover_version(api_url, api_key) + if not self.readarr_version.startswith("0."): + self.api_url = api_url + "/api/v1/{endpoint}?apikey=" + api_key + self._quality_profiles = self.get_all_quality_profiles() + self._metadata_profiles = self.get_all_metadata_profiles() + self._root_folders = self.get_root_folders() + + def discover_version(self, api_url, api_key): + try: + self.api_url = api_url + "/api/v1/{endpoint}?apikey=" + api_key + readarrInfo = self._api_get("system/status") + self.logger.debug( + f"Discovered Readarr version {readarrInfo.get('version')}. Using v1 api." + ) + return readarrInfo.get("version") + except requests.exceptions.HTTPError as e: + self.logger.debug(f"Readarr v1 API threw exception: {e}") + + try: + self.api_url = api_url + "/api/{endpoint}?apikey=" + api_key + readarrInfo = self._api_get("system/status") + self.logger.warning( + f"Discovered Readarr version {readarrInfo.get('version')}. Using legacy API. Consider upgrading to the latest version of Readarr for the best experience." + ) + return readarrInfo.get("version") + except requests.exceptions.HTTPError as e: + self.logger.debug(f"Readarr legacy API threw exception: {e}") + + self.logger.debug("Failed to discover Readarr version") + return None + + def lookup_book(self, title): + r = self._api_get( + "search", {"term": quote(title)} + ) + if not r: + return [] + + return [ + { + "title": x.get("book").get("title"), + "authorId": x.get("book").get("authorId"), + "authorTitle": x.get("book").get("authorTitle"), + "seriesTitle": x.get("book").get("seriesTitle"), + "disambiguation": x.get("book").get("disambiguation"), + "overview": x.get("book").get("overview", "No overview available."), + "remotePoster": x.get("book").get( + "remoteCover", + "https://artworks.thetvdb.com/banners/images/missing/movie.jpg", + ), + "releaseDate": x.get("book").get("releaseDate"), + "foreignBookId": x.get("book").get("foreignBookId"), + "id": x.get("book").get("id"), + "pageCount": x.get("book").get("pageCount"), + "titleSlug": x.get("book").get("titleSlug"), + "images": x.get("book").get("images"), + "links": x.get("book").get("links"), + "author": x.get("book").get("author"), + "editions": x.get("book").get("editions"), + } + for x in r if x.get("book") + ] + + def add_book( + self, + book_info=None, + search=True, + monitored=True, + additional_data={}, + ): + if not book_info: + return False + + if not book_info: + book_info = self.lookup_book(book_info['title']) + if len(book_info): + book_info = book_info[0] + else: + return False + + self.logger.debug(f"Additional data: {additional_data}") + + path = additional_data["p"] + quality = int(additional_data["q"]) + metadata = int(additional_data["m"]) + tags = additional_data.get("t", "") + if len(tags): + tag_ids = [int(x) for x in tags.split(",")] + else: + tag_ids = [] + + params = { + "title": book_info["title"], + "releaseDate": book_info["releaseDate"], + "foreignBookId": book_info["foreignBookId"], + "titleSlug": book_info["titleSlug"], + "monitored": monitored, + "anyEditionOk": True, + "addOptions": {"searchForNewBook": search}, + "editions": book_info["editions"], + "author": { + "qualityProfileId": quality, + "metadataProfileId": metadata, + "foreignAuthorId": book_info["author"]["foreignAuthorId"], + "rootFolderPath": path, + "tags": tag_ids, + } + } + + return self._api_post("book", params) + + def get_root_folders(self): + r = self._api_get("rootfolder", {}) + if not r: + return [] + + return [ + { + "path": x.get("path"), + "freeSpace": x.get("freeSpace"), + "totalSpace": x.get("totalSpace"), + "id": x.get("id"), + } + for x in r + ] + + def _api_get(self, endpoint, params={}): + url = self.api_url.format(endpoint=endpoint) + for k, v in params.items(): + url += f"&{k}={v}" + self.logger.debug(f"Submitting GET request: [{url}]") + r = requests.get(url) + if r.status_code not in [200, 201, 202, 204]: + r.raise_for_status() + return None + else: + return r.json() + + def get_all_tags(self): + r = self._api_get("tag", {}) + self.logger.debug(f"Result of API call to get all tags: {r}") + return [] if not r else r + + def get_filtered_tags(self, allowed_tags): + r = self.get_all_tags() + if not r: + return [] + elif allowed_tags == []: + return [x for x in r if not x["label"].startswith("searcharr-")] + else: + return [ + x + for x in r + if not x["label"].startswith("searcharr-") + and (x["label"] in allowed_tags or x["id"] in allowed_tags) + ] + + def add_tag(self, tag): + params = { + "label": tag, + } + t = self._api_post("tag", params) + self.logger.debug(f"Result of API call to add tag: {t}") + return t + + def get_tag_id(self, tag): + if i := next( + iter( + [ + x.get("id") + for x in self.get_all_tags() + if x.get("label").lower() == tag.lower() + ] + ), + None, + ): + self.logger.debug(f"Found tag id [{i}] for tag [{tag}]") + return i + else: + self.logger.debug(f"No tag id found for [{tag}]; adding...") + t = self.add_tag(tag) + if not isinstance(t, dict): + self.logger.error( + f"Wrong data type returned from Readarr API when attempting to add tag [{tag}]. Expected dict, got {type(t)}." + ) + return None + else: + self.logger.debug( + f"Created tag id for tag [{tag}]: {t['id']}" + if t.get("id") + else f"Could not add tag [{tag}]" + ) + return t.get("id", None) + + def lookup_quality_profile(self, v): + # Look up quality profile from a profile name or id + return next( + (x for x in self._quality_profiles if str(v) in [x["name"], str(x["id"])]), + None, + ) + + def get_all_quality_profiles(self): + return ( + self._api_get("qualityProfile", {}) + ) or None + + def lookup_metadata_profile(self, v): + # Look up metadata profile from a profile name or id + return next( + (x for x in self._metadata_profiles if str(v) in [x["name"], str(x["id"])]), + None, + ) + + def get_all_metadata_profiles(self): + return ( + self._api_get("metadataprofile", {}) + ) or None + + def lookup_root_folder(self, v): + # Look up root folder from a path or id + return next( + (x for x in self._root_folders if str(v) in [x["path"], str(x["id"])]), + None, + ) + + def _api_post(self, endpoint, params={}): + url = self.api_url.format(endpoint=endpoint) + self.logger.debug(f"Submitting POST request: [{url}]; params: [{params}]") + r = requests.post(url, json=params) + if r.status_code not in [200, 201, 202, 204]: + r.raise_for_status() + return None + else: + return r.json() diff --git a/requirements.txt b/requirements.txt index a4c09bc..eb64d0d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ argparse requests python-telegram-bot pyyaml +arrow diff --git a/searcharr.py b/searcharr.py index 76ce0c5..f0aa53b 100644 --- a/searcharr.py +++ b/searcharr.py @@ -1,6 +1,6 @@ """ Searcharr -Sonarr & Radarr Telegram Bot +Sonarr, Radarr & Readarr Telegram Bot By Todd Roberts https://github.com/toddrob99/searcharr """ @@ -12,6 +12,7 @@ from threading import Lock from urllib.parse import parse_qsl import uuid +import arrow from telegram import InlineKeyboardButton, InlineKeyboardMarkup, InputMediaPhoto from telegram.error import BadRequest @@ -20,6 +21,7 @@ from log import set_up_logger import radarr import sonarr +import readarr import settings __version__ = "2.2" @@ -257,6 +259,123 @@ def __init__(self, token): for t in settings.radarr_forced_tags: if t_id := self.radarr.get_tag_id(t): logger.debug(f"Tag id [{t_id}] for forced Radarr tag [{t}]") + self.readarr = ( + readarr.Readarr(settings.readarr_url, settings.readarr_api_key, args.verbose) + if settings.readarr_enabled + else None + ) + if self.readarr: + quality_profiles = [] + if not isinstance(settings.readarr_quality_profile_id, list): + settings.readarr_quality_profile_id = [ + settings.readarr_quality_profile_id + ] + for i in settings.readarr_quality_profile_id: + logger.debug( + f"Looking up/validating readarr quality profile id for [{i}]..." + ) + foundProfile = self.readarr.lookup_quality_profile(i) + if not foundProfile: + logger.error(f"readarr quality profile id/name [{i}] is invalid!") + else: + logger.debug( + f"Found readarr quality profile for [{i}]: [{foundProfile}]" + ) + quality_profiles.append(foundProfile) + if not len(quality_profiles): + logger.warning( + f"No valid readarr quality profile(s) provided! Using all of the quality profiles I found in readarr: {self.readarr._quality_profiles}" + ) + else: + logger.debug( + f"Using the following readarr quality profile(s): {[(x['id'], x['name']) for x in quality_profiles]}" + ) + self.readarr._quality_profiles = quality_profiles + metadata_profiles = [] + if not isinstance(settings.readarr_metadata_profile_id, list): + settings.readarr_metadata_profile_id = [ + settings.readarr_metadata_profile_id + ] + for i in settings.readarr_metadata_profile_id: + logger.debug( + f"Looking up/validating readarr metadata profile id for [{i}]..." + ) + foundProfile = self.readarr.lookup_metadata_profile(i) + if not foundProfile: + logger.error(f"readarr metadata profile id/name [{i}] is invalid!") + else: + logger.debug( + f"Found readarr metadata profile for [{i}]: [{foundProfile}]" + ) + metadata_profiles.append(foundProfile) + if not len(metadata_profiles): + logger.warning( + f"No valid readarr metadata profile(s) provided! Using all of the metadata profiles I found in readarr: {self.readarr._metadata_profiles}" + ) + else: + logger.debug( + f"Using the following readarr metadata profile(s): {[(x['id'], x['name']) for x in metadata_profiles]}" + ) + self.readarr._metadata_profiles = metadata_profiles + + root_folders = [] + if not hasattr(settings, "readarr_book_paths"): + settings.readarr_book_paths = [] + logger.warning( + 'No readarr_movie_paths setting detected. Please set one in settings.py (readarr_movie_paths=["/path/1", "/path/2"]). Proceeding with all root folders configured in readarr.' + ) + if not isinstance(settings.readarr_book_paths, list): + settings.readarr_book_paths = [settings.readarr_book_paths] + for i in settings.readarr_book_paths: + logger.debug(f"Looking up/validating readarr root folder for [{i}]...") + foundPath = self.readarr.lookup_root_folder(i) + if not foundPath: + logger.error(f"readarr root folder path/id [{i}] is invalid!") + else: + logger.debug(f"Found readarr root folder for [{i}]: [{foundPath}]") + root_folders.append(foundPath) + if not len(root_folders): + logger.warning( + f"No valid readarr root folder(s) provided! Using all of the root folders I found in readarr: {self.readarr._root_folders}" + ) + else: + logger.debug( + f"Using the following readarr root folder(s): {[(x['id'], x['path']) for x in root_folders]}" + ) + self.readarr._root_folders = root_folders + if not hasattr(settings, "readarr_tag_with_username"): + settings.readarr_tag_with_username = True + logger.warning( + "No readarr_tag_with_username setting found. Please add readarr_tag_with_username to settings.py (readarr_tag_with_username=True or readarr_tag_with_username=False). Defaulting to True." + ) + if not hasattr(settings, "readarr_movie_command_aliases"): + settings.readarr_book_command_aliases = ["book"] + logger.warning( + 'No readarr_book_command_aliases setting found. Please add readarr_movie_command_aliases to settings.py (e.g. readarr_book_command_aliases=["book", "bk"]. Defaulting to ["book"].' + ) + if not hasattr(settings, "readarr_forced_tags"): + settings.readarr_forced_tags = [] + logger.warning( + 'No readarr_forced_tags setting found. Please add readarr_forced_tags to settings.py (e.g. readarr_forced_tags=["tag-1", "tag-2"]) if you want specific tags added to each movie. Defaulting to empty list ([]).' + ) + if not hasattr(settings, "readarr_allow_user_to_select_tags"): + settings.readarr_allow_user_to_select_tags = True + logger.warning( + "No readarr_allow_user_to_select_tags setting found. Please add readarr_allow_user_to_select_tags to settings.py (e.g. readarr_allow_user_to_select_tags=False) if you do not want users to be able to select tags when adding a movie. Defaulting to True." + ) + if not hasattr(settings, "readarr_user_selectable_tags"): + settings.readarr_user_selectable_tags = [] + logger.warning( + 'No readarr_user_selectable_tags setting found. Please add readarr_user_selectable_tags to settings.py (e.g. readarr_user_selectable_tags=["tag-1", "tag-2"]) if you want to limit the tags a user can select. Defaulting to empty list ([]), which will present the user with all tags.' + ) + for t in settings.readarr_user_selectable_tags: + if t_id := self.readarr.get_tag_id(t): + logger.debug( + f"Tag id [{t_id}] for user-selectable readarr tag [{t}]" + ) + for t in settings.readarr_forced_tags: + if t_id := self.readarr.get_tag_id(t): + logger.debug(f"Tag id [{t_id}] for forced readarr tag [{t}]") self.conversations = {} if not hasattr(settings, "searcharr_admin_password"): @@ -326,6 +445,77 @@ def cmd_start(self, update, context): else: update.message.reply_text(self._xlate("incorrect_pw")) + def cmd_book(self, update, context): + logger.debug(f"Received book cmd from [{update.message.from_user.username}]") + if not self._authenticated(update.message.from_user.id): + update.message.reply_text( + self._xlate( + "auth_required", + commands=" OR ".join( + [ + f"`/{c} <{self._xlate('password')}>`" + for c in settings.searcharr_start_command_aliases + ] + ), + ) + ) + return + if not settings.radarr_enabled: + update.message.reply_text(self._xlate("readarr_disabled")) + return + title = self._strip_entities(update.message) + if not len(title): + x_title = self._xlate("title").title() + update.message.reply_text( + self._xlate( + "include_book_title_in_cmd", + commands=" OR ".join( + [ + f"`/{c} {x_title}`" + for c in settings.readarr_book_command_aliases + ] + ), + ) + ) + return + results = self.readarr.lookup_book(title) + cid = self._generate_cid() + # self.conversations.update({cid: {"cid": cid, "type": "book", "results": results}}) + self._create_conversation( + id=cid, + username=str(update.message.from_user.username), + kind="book", + results=results, + ) + + if not len(results): + update.message.reply_text(self._xlate("no_matching_books")) + else: + r = results[0] + reply_message, reply_markup = self._prepare_response( + "book", r, cid, 0, len(results) + ) + try: + context.bot.sendPhoto( + chat_id=update.message.chat.id, + photo=r["remotePoster"], + caption=reply_message, + reply_markup=reply_markup, + ) + except BadRequest as e: + if str(e) in self._bad_request_poster_error_messages: + logger.error( + f"Error sending photo [{r['remotePoster']}]: BadRequest: {e}. Attempting to send with default poster..." + ) + context.bot.sendPhoto( + chat_id=update.message.chat.id, + photo="https://artworks.thetvdb.com/banners/images/missing/movie.jpg", + caption=reply_message, + reply_markup=reply_markup, + ) + else: + raise + def cmd_movie(self, update, context): logger.debug(f"Received movie cmd from [{update.message.from_user.username}]") if not self._authenticated(update.message.from_user.id): @@ -579,7 +769,7 @@ def callback(self, update, context): # self.conversations.pop(cid) query.message.delete() elif op == "prev": - if convo["type"] in ["series", "movie"]: + if convo["type"] in ["series", "movie", "book"]: if i <= 0: query.answer() return @@ -628,7 +818,7 @@ def callback(self, update, context): reply_markup=reply_markup, ) elif op == "next": - if convo["type"] in ["series", "movie"]: + if convo["type"] in ["series", "movie", "book"]: if i >= len(convo["results"]): query.answer() return @@ -687,6 +877,8 @@ def callback(self, update, context): if convo["type"] == "series" else self.radarr._root_folders if convo["type"] == "movie" + else self.readarr._root_folders + if convo["type"] == "book" else [] ) if not additional_data.get("p"): @@ -737,7 +929,7 @@ def callback(self, update, context): self._xlate( "no_root_folders", kind=self._xlate(convo["type"]), - app="Sonarr" if convo["type"] == "series" else "Radarr", + app="Sonarr" if convo["type"] == "series" else "Radarr" if convo['type'] == 'movie' else 'Readarr', ) ) query.message.delete() @@ -770,6 +962,8 @@ def callback(self, update, context): self.sonarr._quality_profiles if convo["type"] == "series" else self.radarr._quality_profiles + if convo["type"] == "movie" + else self.readarr._quality_profiles ) if len(quality_profiles) > 1: # prepare response to prompt user to select quality profile, and return @@ -819,7 +1013,64 @@ def callback(self, update, context): self._xlate( "no_quality_profiles", kind=self._xlate(convo["type"]), - app="Sonarr" if convo["type"] == "series" else "Radarr", + app="Sonarr" if convo["type"] == "series" else "Radarr" if convo['type'] == 'movie' else 'Readarr', + ) + ) + query.message.delete() + query.answer() + return + + if convo['type'] == 'book' and not additional_data.get("m"): + metadata_profiles = self.readarr._metadata_profiles + if len(metadata_profiles) > 1: + # prepare response to prompt user to select quality profile, and return + reply_message, reply_markup = self._prepare_response( + convo["type"], + r, + cid, + i, + len(convo["results"]), + add=True, + metadata_profiles=metadata_profiles, + ) + try: + query.message.edit_media( + media=InputMediaPhoto(r["remotePoster"]), + reply_markup=reply_markup, + ) + except BadRequest as e: + if str(e) in self._bad_request_poster_error_messages: + logger.error( + f"Error sending photo [{r['remotePoster']}]: BadRequest: {e}. Attempting to send with default poster..." + ) + query.message.edit_media( + media=InputMediaPhoto( + "https://artworks.thetvdb.com/banners/images/missing/movie.jpg" + ), + reply_markup=reply_markup, + ) + else: + raise + query.bot.edit_message_caption( + chat_id=query.message.chat_id, + message_id=query.message.message_id, + caption=reply_message, + reply_markup=reply_markup, + ) + query.answer() + return + elif len(metadata_profiles) == 1: + logger.debug( + f"Only one metadata profile enabled. Adding/Updating additional data for cid=[{cid}], key=[m], value=[{metadata_profiles[0]['id']}]..." + ) + self._update_add_data(cid, "m", metadata_profiles[0]["id"]) + else: + self._delete_conversation(cid) + query.message.reply_text( + self._xlate( + "no_metadata_profiles", + kind=self._xlate(convo["type"]), + app="Sonarr" if convo["type"] == "series" else "Radarr" if convo['type'] == 'movie' else 'Readarr', ) ) query.message.delete() @@ -886,10 +1137,16 @@ def callback(self, update, context): ) allow_user_to_select_tags = settings.radarr_allow_user_to_select_tags forced_tags = settings.radarr_forced_tags + elif convo["type"] == "book": + all_tags = self.readarr.get_filtered_tags( + settings.readarr_user_selectable_tags + ) + allow_user_to_select_tags = settings.readarr_allow_user_to_select_tags + forced_tags = settings.readarr_forced_tags if allow_user_to_select_tags and not additional_data.get("td"): if not len(all_tags): logger.warning( - f"User tagging is enabled, but no tags found. Make sure there are tags in {'Sonarr' if convo['type'] == 'series' else 'Radarr'} matching your Searcharr configuration." + f"User tagging is enabled, but no tags found. Make sure there are tags in {'Sonarr' if convo['type'] == 'series' else 'Radarr' if convo['type'] == 'movie' else 'Readarr'} matching your Searcharr configuration." ) elif not additional_data.get("tt"): reply_message, reply_markup = self._prepare_response( @@ -950,6 +1207,9 @@ def callback(self, update, context): elif convo["type"] == "movie": get_tag_id = self.radarr.get_tag_id tag_with_username = settings.radarr_tag_with_username + elif convo["type"] == "book": + get_tag_id = self.readarr.get_tag_id + tag_with_username = settings.readarr_tag_with_username if tag_with_username: tag = f"searcharr-{query.from_user.username if query.from_user.username else query.from_user.id}" if tag_id := get_tag_id(tag): @@ -984,6 +1244,13 @@ def callback(self, update, context): min_avail=settings.radarr_min_availability, additional_data=self._get_add_data(cid), ) + elif convo["type"] == "book": + added = self.readarr.add_book( + book_info=r, + monitored=settings.readarr_add_monitored, + search=settings.readarr_search_on_add, + additional_data=self._get_add_data(cid), + ) else: added = False except Exception as e: @@ -1151,6 +1418,7 @@ def _prepare_response( add=False, paths=None, quality_profiles=None, + metadata_profiles=None, monitor_options=None, tags=None, ): @@ -1174,12 +1442,20 @@ def _prepare_response( "TMDB", url=f"https://www.themoviedb.org/movie/{r['tmdbId']}" ) ) - if r["imdbId"]: - keyboardNavRow.append( - InlineKeyboardButton( - "IMDb", url=f"https://imdb.com/title/{r['imdbId']}" + elif kind == "book" and r["links"]: + for link in r["links"]: + keyboardNavRow.append( + InlineKeyboardButton( + link["name"], url=link["url"] + ) + ) + if kind == "series" or kind == "movie": + if r["imdbId"]: + keyboardNavRow.append( + InlineKeyboardButton( + "IMDb", url=f"https://imdb.com/title/{r['imdbId']}" + ) ) - ) if total_results > 1 and i < total_results - 1: keyboardNavRow.append( InlineKeyboardButton( @@ -1227,6 +1503,16 @@ def _prepare_response( ) ], ) + elif metadata_profiles: + for m in metadata_profiles: + keyboard.append( + [ + InlineKeyboardButton( + self._xlate("add_metadata_button", metadata=m["name"]), + callback_data=f"{cid}^^^{i}^^^add^^m={m['id']}", + ) + ], + ) elif paths: for p in paths: keyboard.append( @@ -1282,6 +1568,11 @@ def _prepare_response( reply_message = f"{r['title']}{' (' + str(r['year']) + ')' if r['year'] and str(r['year']) not in r['title'] else ''}{' - ' + str(r['runtime']) + ' min' if r['runtime'] else ''} - {r['status'].title()}\n\n{r['overview']}"[ 0:1024 ] + elif kind == "book": + release = arrow.get(r["releaseDate"]) + reply_message = f"{r['title']}{' - ' + r['disambiguation'] if r['disambiguation'] else ''}{' - ' + r['seriesTitle'] if r['seriesTitle'] else ''} ({release.format('MMMM DD, YYYY')})\n\n{r['overview']}"[ + 0:1024 + ] else: reply_message = self._xlate("unexpected_error") @@ -1378,12 +1669,23 @@ def cmd_help(self, update, context): ] ), ) - if settings.sonarr_enabled and settings.radarr_enabled: - resp = f"{sonarr_help} {radarr_help}" + readarr_help = self._xlate( + "help_readarr", + book_commands=" OR ".join( + [ + f"`/{c} {self._xlate('title').title()}`" + for c in settings.readarr_book_command_aliases + ] + ), + ) + if settings.sonarr_enabled and settings.radarr_enabled and settings.readarr_enabled: + resp = f"{sonarr_help} {radarr_help} {readarr_help}" elif settings.sonarr_enabled: resp = sonarr_help elif settings.radarr_enabled: resp = radarr_help + elif settings.readarr_enabled: + resp = readarr_help else: resp = self._xlate("no_features") @@ -1417,6 +1719,9 @@ def run(self): for c in settings.searcharr_start_command_aliases: logger.debug(f"Registering [/{c}] as a start command") updater.dispatcher.add_handler(CommandHandler(c, self.cmd_start)) + for c in settings.readarr_book_command_aliases: + logger.debug(f"Registering [/{c}] as a book command") + updater.dispatcher.add_handler(CommandHandler(c, self.cmd_book)) for c in settings.radarr_movie_command_aliases: logger.debug(f"Registering [/{c}] as a movie command") updater.dispatcher.add_handler(CommandHandler(c, self.cmd_movie)) diff --git a/settings-sample.py b/settings-sample.py index 5efa933..39c573a 100644 --- a/settings-sample.py +++ b/settings-sample.py @@ -1,6 +1,6 @@ """ Searcharr -Sonarr & Radarr Telegram Bot +Sonarr, Radarr & Readarr Telegram Bot By Todd Roberts https://github.com/toddrob99/searcharr """ @@ -45,3 +45,18 @@ radarr_min_availability = "released" # options: "announced", "inCinemas", "released" radarr_movie_command_aliases = ["movie"] # e.g. ["movie", "mv", "m"] radarr_movie_paths = [] # e.g. ["/movies", "/other-movies"] - can be full path or id value - leave empty to enable all + +# Readarr +readarr_enabled = True +readarr_url = "" # http://192.168.0.100:8787 +readarr_api_key = "" +readarr_quality_profile_id = ["eBook", "Spoken"] # can be name or id value - include multiple to allow the user to choose +readarr_metadata_profile_id = ["Standard"] # can be name or id value - include multiple to allow the user to choose +readarr_add_monitored = True +readarr_search_on_add = True +readarr_tag_with_username = True +readarr_forced_tags = [] # e.g. ["searcharr", "friends-and-family"] - leave empty for none +readarr_allow_user_to_select_tags = True +readarr_user_selectable_tags = [] # e.g. ["custom-tag-1", "custom-tag-2"] - leave empty to let user choose from all tags in Readarr +readarr_book_command_aliases = ["book"] # e.g. ["book", "bk", "b"] +readarr_book_paths = [] # e.g. ["/books", "/other-books"] - can be full path or id value - leave empty to enable all diff --git a/sonarr.py b/sonarr.py index 909d245..801cc8e 100644 --- a/sonarr.py +++ b/sonarr.py @@ -1,6 +1,6 @@ """ Searcharr -Sonarr & Radarr Telegram Bot +Sonarr, Radarr & Readarr Telegram Bot Sonarr API Wrapper By Todd Roberts https://github.com/toddrob99/searcharr From 3df9caaf4e36311bbea2eaefc5dd3c35f7517b4f Mon Sep 17 00:00:00 2001 From: todd <toddrob@gmail.com> Date: Thu, 13 Oct 2022 19:39:29 -0400 Subject: [PATCH 05/16] code formatting w/ black fix issues with merged PRs translations for Readarr --- .vs/searcharr-languageProfile/v16/.suo | Bin 3584 -> 0 bytes .vs/slnx.sqlite | Bin 90112 -> 0 bytes lang/ca-es.yml | 9 + lang/de-de.yml | 9 + lang/en-us.yml | 2 +- lang/es-es.yml | 9 + lang/fr-fr.yml | 13 +- lang/lt-lt.yml | 13 +- lang/pt-br.yml | 11 +- lang/ro.yml | 9 + lang/ru-ru.yml | 9 + radarr.py | 10 +- readarr.py | 29 +-- searcharr.py | 260 ++++++++++++++----------- settings-sample.py | 2 +- sonarr.py | 15 +- 16 files changed, 258 insertions(+), 142 deletions(-) delete mode 100644 .vs/searcharr-languageProfile/v16/.suo delete mode 100644 .vs/slnx.sqlite diff --git a/.vs/searcharr-languageProfile/v16/.suo b/.vs/searcharr-languageProfile/v16/.suo deleted file mode 100644 index b651f0ba26abd36c3383279b02d32c4d1b8dba02..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3584 zcmca`Uhu)fjZzO8(10BSGsD0CoD6J8;*3Bx6O1pwz`z1zgT(&*|NkE(%nZap<|r6K zA@CRIR#5znf{_^lK@9moSi+#d;L4B(q>C6TL0Oq-AjZIolIMxh2rDm$QA>;(<d`Ey zJ+;*gW&5ca|Dd`69}tL`Cdah#1|us-5ma#iFvaCFWHERGQ(qd8<;qZj&j<`TF$N|^ zQ22xDB@m6F0E+;qo&r{1VBpVC3QYAm44FXtK{%Zu7|2fq;#7t(215oj26G^Z8)yJ) zM3B8OfLkT1%pO!OMLd|@AevNzV0uA$5>^+C)(5aq8U8dV8-nr<D9%UYefY;Wx)0kJ zV;Eu>a)G5?3b6c3WGDu*iWthkBuG3HD3T0RkpdJc1JWS*M4*afh72%W#Gnf-Jrltt zXDN_Q2g*ar^faK}93T&(rvzA{Ldw@-ppIgo%3Ov5u)H3Su4Jg)5!w0f^&)re*$oS? zBpi5VSCYu9|4RAb;s}*Ty3;c=LFF2{+d*QT_SK<NYNXW6w7*Yl-^KG3BsL0&4*>vD CSzy2b diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite deleted file mode 100644 index b5279996e9e60d3d670201915244995715b784c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 90112 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 diff --git a/lang/ca-es.yml b/lang/ca-es.yml index d6f6442..30cf227 100644 --- a/lang/ca-es.yml +++ b/lang/ca-es.yml @@ -4,6 +4,7 @@ language_label: Català movie: pel·lícula series: sèrie season: temporada +book: llibre title: títol title_here: títol aquí password: contrasenya @@ -25,6 +26,7 @@ convo_not_found: He rebut la teva ordre, però no reconec la conversa. Sisplau, search_canceled: Cerca cancel·lada! no_root_folders: "Error afegint {kind}: no hi han carpetes arrel per {app}! Sisplau, comprova la teva configuració de Searcharr i torna a intentar-ho." no_quality_profiles: "Error afegint {kind}: no hi han perfils de qualitat activats per {app}! Sisplau, comprova la teva configuració de Searcharr i torna a intentar-ho." +no_language_profiles: "Error afegint {kind}: no hi han perfils de llenguatge activats per {app}! Sisplau, comprova la teva configuració de Searcharr i torna a intentar-ho." all_seasons: Totes les Temporades first_season: Només la Primera Temporada latest_season: Només la Última Temporada @@ -42,6 +44,7 @@ add_tag_button: "Afegir Etiqueta: {tag}" finished_tagging_button: Etiquetat Completat monitor_button: Monitoratge {option} add_quality_button: "Afegir Qualitat: {quality}" +add_language_button: "Afegir llenguatge: {language}" add_path_button: Afegir a {path} add_button: "{kind} afegit!" already_added_button: "Ja Afegit!" @@ -57,3 +60,9 @@ help_sonarr: Utilitza {series_commands} per afegir una sèrie a Sonarr. help_radarr: Utilitza {movie_commands} per afegir una pel·lícula a Radarr. no_features: Ho sento, però totes les meves funcions estàn desactivades temporalment. admin_help: Com que ets administrador, també pots utilitzar {commands} per gestionar els usuaris. +readarr_disabled: Ho sento, però el suport per llibres està desactivat. +include_book_title_in_cmd: Sisplau, inclou el títol del llibre en la ordre, per exemple {commands} +no_matching_books: Ho sento, però no he trobat cap llibre que compleixi el criteri de cerca. +help_readarr: Utilitza {book_commands} per afegir una llibre a Readarr. +no_metadata_profiles: "Error afegint {kind}: no hi han perfils de metadata activats per {app}! Sisplau, comprova la teva configuració de Searcharr i torna a intentar-ho." +add_metadata_button: "Afegir Metadata: {metadata}" \ No newline at end of file diff --git a/lang/de-de.yml b/lang/de-de.yml index 7b1dff4..30239df 100644 --- a/lang/de-de.yml +++ b/lang/de-de.yml @@ -4,6 +4,7 @@ language_label: German movie: Film series: Serie season: Staffel +book: Buchen title: Title title_here: Titel hier password: Kennwort @@ -25,6 +26,7 @@ convo_not_found: Ich habe Ihren Befehl erhalten, aber ich erkenne das Gespräch search_canceled: Suche abgebrochen! no_root_folders: "Fehler beim Hinzufügen {kind}: keine Root-Ordner aktiviert für {app}! Bitte überprüfen Sie Ihre Searcharr-Konfiguration und versuchen Sie es erneut." no_quality_profiles: "Fehler beim Hinzufügen {kind}: keine Qualitätsprofile aktiviert für {app}! Bitte überprüfen Sie Ihre Searcharr-Konfiguration und versuchen Sie es erneut." +no_language_profiles: "Fehler beim Hinzufügen {kind}: keine Sprachprofil aktiviert für {app}! Bitte überprüfen Sie Ihre Searcharr-Konfiguration und versuchen Sie es erneut." all_seasons: Alle Staffel first_season: Nur die erste Staffel latest_season: Nur letzte Staffel @@ -42,6 +44,7 @@ add_tag_button: "Etikette hinzufügen: {tag}" finished_tagging_button: Fertiges Etikette monitor_button: überwachen {option} add_quality_button: "Qualität hinzufügen: {quality}" +add_language_button: "Sprache hinzufügen: {language}" add_path_button: Hinzufügen zu {path} add_button: Hinzufügen {kind}! already_added_button: Schon hinzugefügt! @@ -57,3 +60,9 @@ help_sonarr: Verwenden Sie {series_commands} um Sonarr eine Serie hinzuzufügen. help_radarr: Verwenden Sie {movie_commands} um einen Film zu Radarr hinzuzufügen. no_features: Tut mir leid, aber alle meine Funktionen sind derzeit deaktiviert. admin_help: Da Sie ein Administrator sind, können Sie auch {commands} verwenden, um Benutzer zu verwalten. +readarr_disabled: Tut mir leid, aber die Buchunterstützung ist deaktiviert. +include_book_title_in_cmd: Bitte geben Sie den Buchtitel in den Befehl ein, z.b. {commands} +no_matching_books: Tut mir leid, aber ich habe keine passenden Buchen gefunden. +help_readarr: Verwenden Sie {book_commands} um einen Buchen zu Readarr hinzuzufügen. +no_metadata_profiles: "Fehler beim Hinzufügen {kind}: keine Metadatenprofil aktiviert für {app}! Bitte überprüfen Sie Ihre Searcharr-Konfiguration und versuchen Sie es erneut." +add_metadata_button: "Metadaten hinzufügen: {metadata}" \ No newline at end of file diff --git a/lang/en-us.yml b/lang/en-us.yml index 7caa2e9..d914d88 100644 --- a/lang/en-us.yml +++ b/lang/en-us.yml @@ -44,7 +44,7 @@ add_tag_button: "Add Tag: {tag}" finished_tagging_button: Finished Tagging monitor_button: Monitor {option} add_quality_button: "Add Quality: {quality}" -select_language_button: "Select Language: {language}" +add_language_button: "Add Language: {language}" add_path_button: Add to {path} add_button: Add {kind}! already_added_button: Already Added! diff --git a/lang/es-es.yml b/lang/es-es.yml index a979830..ba1eb0c 100644 --- a/lang/es-es.yml +++ b/lang/es-es.yml @@ -4,6 +4,7 @@ language_label: Español movie: película series: serie season: temporada +book: libro title: título title_here: título aquí password: contraseña @@ -25,6 +26,7 @@ convo_not_found: He recibido tu comando, pero no he reconocido la conversación. search_canceled: ¡Búsqueda cancelada! no_root_folders: "¡Error añadiendo {kind}: no se han encontrado carpetas raíz para {app}! Por favor, consulta tu configuración de Searcharr e inténtalo de nuevo." no_quality_profiles: "¡Error añadiendo {kind}: no se han encontrado perfiles de calidad activados para {app}! Por favor, consulta tu configuración de Searcharr e inténtalo de nuevo." +no_language_profiles: "¡Error añadiendo {kind}: no se han encontrado perfiles de idioma activados para {app}! Por favor, consulta tu configuración de Searcharr e inténtalo de nuevo." all_seasons: Todas las Temporadas first_season: Sólo la Primera Temporada latest_season: Sólo la Última Temporada @@ -42,6 +44,7 @@ add_tag_button: "Añadir Etiqueta: {tag}" finished_tagging_button: Etiquetado Finalizado monitor_button: Monitoraje {option} add_quality_button: "Añadir Calidad: {quality}" +add_language_button: "Añadir Idioma: {language}" add_path_button: Añadir a {path} add_button: ¡Añadir {kind}! already_added_button: ¡Ya Añadido! @@ -57,3 +60,9 @@ help_sonarr: Usa {series_commands} para añadir serie a Sonarr. help_radarr: Usa {movie_commands} para añadir película a Radarr. no_features: Lo siento, pero todas mis funciones están actualmente desactivadas. admin_help: Ya que eres administrador, también puedes usar {commands} para gestionar los usuarios. +readarr_disabled: Lo siento, pero el soporte para libros está desativado. +include_book_title_in_cmd: Por favor, incluye el título del libro en el comando, por ejemplo {commands} +no_matching_books: Lo siento, no he encontrado ninguna libro que cumpla el criterio de búsqueda. +help_readarr: Usa {book_commands} para añadir libro a Readarr. +no_metadata_profiles: "¡Error añadiendo {kind}: no se han encontrado perfiles de metadata activados para {app}! Por favor, consulta tu configuración de Searcharr e inténtalo de nuevo." +add_metadata_button: "Añadir Metadata: {metadata}" \ No newline at end of file diff --git a/lang/fr-fr.yml b/lang/fr-fr.yml index fe65308..833a2c1 100644 --- a/lang/fr-fr.yml +++ b/lang/fr-fr.yml @@ -4,6 +4,7 @@ language_label: Français movie: film series: séries season: saison +book: livre title: titre title_here: titre ici password: mot de passe @@ -23,8 +24,9 @@ no_matching_series: Désolé, mais je n'ai pas trouvé de série correspondante. no_users_found: Désolé, mais je n'ai pas trouvé d'utilisateurs. Cela semble être une erreur... convo_not_found: J'ai reçu votre commande, mais je ne reconnais pas la conversation. Veuillez recommencer. search_canceled: Recherche annulée ! -no_root_folders: "Error adding {kind} : no root folders enabled for {app} ! Veuillez vérifier votre configuration Searcharr et réessayer." -no_quality_profiles: "Error adding {kind} : no quality profiles enabled for {app} ! Veuillez vérifier votre configuration Searcharr et réessayer." +no_root_folders: "Erreur lors de l'ajout {kind} : aucun dossier racine activé pour {app} ! Veuillez vérifier votre configuration Searcharr et réessayer." +no_quality_profiles: "Erreur lors de l'ajout {kind} : aucun profil de qualité activé pour {app} ! Veuillez vérifier votre configuration Searcharr et réessayer." +no_language_profiles: "Erreur lors de l'ajout {kind} : aucun profil de langue activé pour {app} ! Veuillez vérifier votre configuration Searcharr et réessayer." all_seasons: Toutes saisons first_season: Première saison latest_season: Dernière saison @@ -42,6 +44,7 @@ add_tag_button: "Ajout Tag: {tag}" finished_tagging_button: Tag terminé monitor_button: Monitor {option} add_quality_button: "Qualité : {quality}" +add_language_button: "Langue: {language}" add_path_button: Chemin {path} add_button: ajouter {kind}! already_added_button: Déjà téléchargé! @@ -57,3 +60,9 @@ help_sonarr: Utilisez {series_commands} pour ajouter une série à Sonarr. help_radarr: Utilisez {movie_commands} pour ajouter un film à Radarr. no_features: Désolé, mais toutes mes fonctions sont actuellement désactivées. admin_help: Puisque vous êtes un administrateur, vous pouvez également utiliser les {commands} pour gérer les utilisateurs. +readarr_disabled: Désolé, mais le support des livres est désactivé. +include_book_title_in_cmd: "Veuillez inclure le titre du livre dans la commande, par exemple : {commands}" +no_matching_books: Désolé, mais je n'ai pas trouvé de livres correspondants. +help_readarr: Utilisez {book_commands} pour ajouter un livre à Readarr. +no_metadata_profiles: "Erreur lors de l'ajout {kind} : aucun profil de metadata activé pour {app} ! Veuillez vérifier votre configuration Searcharr et réessayer." +add_metadata_button: "Metadata: {metadata}" \ No newline at end of file diff --git a/lang/lt-lt.yml b/lang/lt-lt.yml index 0a74bd5..984bf4c 100644 --- a/lang/lt-lt.yml +++ b/lang/lt-lt.yml @@ -4,6 +4,7 @@ language_label: Lietuvių movie: filmai series: serialai season: sezonas +book: knyga title: pavadinimas title_here: pavadinimas čia password: slaptažodis @@ -25,10 +26,11 @@ convo_not_found: Aš gavau komandą, bet negaliu jos įvykdyti. Prašau pabandyk search_canceled: Paieška atšaukta! no_root_folders: "Klaida {kind}: neaprašyta {app} šakninis aplankas! Patikrinkite Searcharr konfigūraciją ir bandykite dar kartą." no_quality_profiles: "Klaida {kind}: neaprašyti {app} kokybės profiliai! Patikrinkite Searcharr konfigūraciją ir bandykite dar kartą." +no_language_profiles: "Klaida {kind}: neaprašyti {app} kalbos profiliai! Patikrinkite Searcharr konfigūraciją ir bandykite dar kartą." all_seasons: Visus sezonus first_season: Tik pirmą sezoną latest_season: Tik paskutinį sezoną -added: {title} sėkmingai įtrauktas! +added: "{title} sėkmingai įtrauktas!" unknown_error_adding: Bandant įtraukti įvyko nežinoma klaida {kind}! removed_user: Vartotojas [{user}] sėkmingai pašalintas! unknown_error_removing_user: Bandant pašalinti [{user}] įvyko nežinoma klaida! @@ -42,6 +44,7 @@ add_tag_button: "Pridėti žymą: {tag}" finished_tagging_button: Žymėjimas baigtas monitor_button: Stebėti {option} add_quality_button: "Pridėti kokybę: {quality}" +add_language_button: "Pridėti kalbą: {quality}" add_path_button: Įtraukti į {path} add_button: Pridėti {kind}! already_added_button: Pridėta! @@ -54,6 +57,12 @@ remove_admin_button: Pašalinti administratorių done: Atlikta listing_users_pagination: Searcharr vartotojų sąrašas {page_info}. help_sonarr: Naudokite {series_commands} norėdami įtraukti serialą į Sonarr. -help_radarr: naudokite {movie_commands} norėdami įtraukti filmą į Radarr. +help_radarr: Naudokite {movie_commands} norėdami įtraukti filmą į Radarr. no_features: Atsiprašome, bet šiuo metu visos funkcijos išjungtos. admin_help: Kadangi esate administratorius, galite administruoti vartotojus komandų {commands} pagalba. +readarr_disabled: Atsiprašau, bet negalima ieškoti knygų. +include_book_title_in_cmd: Prašome kartu su komanda parašyti knyga pavadinimą, pvz. {commands} +no_matching_books: Atsiprašau, bet toks knygos nerastas. +help_readarr: Naudokite {book_commands} norėdami įtraukti knyga į Readarr. +no_metadata_profiles: "Klaida {kind}: neaprašyti {app} metaduomenų profiliai! Patikrinkite Searcharr konfigūraciją ir bandykite dar kartą." +add_metadata_button: "Metadata: {metadata}" \ No newline at end of file diff --git a/lang/pt-br.yml b/lang/pt-br.yml index a9911ae..211962b 100644 --- a/lang/pt-br.yml +++ b/lang/pt-br.yml @@ -4,6 +4,7 @@ language_label: Brazilian Portuguese movie: filme series: séries season: temporada +book: livro title: título title_here: título aqui password: senha @@ -25,6 +26,7 @@ convo_not_found: Recebi seu comando, mas não reconheço a conversa. Por favor, search_canceled: Pesquisa cancelada! no_root_folders: "Erro ao adicionar {kind}: nenhuma pasta raiz habilitada para {app}! Verifique a configuração do Searcharr e tente novamente." no_quality_profiles: "Erro ao adicionar {kind}: nenhum perfil de qualidade habilitado para {app}! Verifique a configuração do Searcharr e tente novamente." +no_language_profiles: "Erro ao adicionar {kind}: nenhum perfil de idioma habilitado para {app}! Verifique a configuração do Searcharr e tente novamente." all_seasons: Todas temporadas first_season: Apenas a primeira temporada latest_season: Apenas a última temporada @@ -42,6 +44,7 @@ add_tag_button: "Add Tag: {tag}" finished_tagging_button: Marcação concluída monitor_button: Monitorar {option} add_quality_button: "Add Qualidade: {quality}" +add_language_button: "Add Idioma: {language}" add_path_button: Add para {path} add_button: Add {kind}! already_added_button: Já adicionado! @@ -56,4 +59,10 @@ listing_users_pagination: Listando usuários do Searcharr {page_info}. help_sonarr: Use {series_commands} para adicionar uma série ao Sonarr. help_radarr: Use {movie_commands} para adicionar um filme ao Radarr. no_features: Desculpe, mas todos os meus recursos estão desativados no momento. -admin_help: Como você é um administrador, você também pode usar {commands} para gerenciar usuários. \ No newline at end of file +admin_help: Como você é um administrador, você também pode usar {commands} para gerenciar usuários. +readarr_disabled: Desculpe, mas o suporte a filmes está desativado. +include_book_title_in_cmd: Por favor, inclua o título do livro no comando, por exemplo. {commands} +no_matching_books: Desculpe, mas não encontrei nenhum livro correspondente. +help_readarr: Use {book_commands} para adicionar um livro ao Readarr. +no_metadata_profiles: "Erro ao adicionar {kind}: nenhum perfil de metadados habilitado para {app}! Verifique a configuração do Searcharr e tente novamente." +add_metadata_button: "Add Metadados: {metadata}" \ No newline at end of file diff --git a/lang/ro.yml b/lang/ro.yml index 573a7cd..093c7ea 100644 --- a/lang/ro.yml +++ b/lang/ro.yml @@ -4,6 +4,7 @@ language_label: Romanian movie: film series: serial season: sezon +book: carte title: titlu title_here: titlu aici password: parola @@ -25,6 +26,7 @@ convo_not_found: Am primit comanda ta, dar nu recunosc conversația. Vă rugăm search_canceled: Căutare anulată! no_root_folders: "Eroare la adăugare {kind}: nu sunt activate foldere rădăcină {app}! Verificați configurația Searcharr și încercați din nou." no_quality_profiles: "Eroare la adăugare {kind}: nu sunt activate profiluri de calitate pentru {app}! Verificați configurația Searcharr și încercați din nou." +no_language_profiles: "Eroare la adăugare {kind}: nu sunt activate profiluri de limba pentru {app}! Verificați configurația Searcharr și încercați din nou." all_seasons: Toate sezoanele first_season: Doar primul sezon latest_season: Numai ultimul sezon @@ -42,6 +44,7 @@ add_tag_button: "Adaugă etichetă: {tag}" finished_tagging_button: Etichetarea terminată monitor_button: Monitorizare {option} add_quality_button: "Adăugați calitate: {quality}" +add_language_button: "Adăugați limba: {language}" add_path_button: Adaugă la {path} add_button: Adauga {kind}! already_added_button: Deja Adaugat! @@ -57,3 +60,9 @@ help_sonarr: Foloseste {series_commands} pentru a adăuga un serial la Sonarr. help_radarr: Foloseste {movie_commands} pentru a adăuga un film la Radarr. no_features: Ne pare rău, dar toate funcțiile mele sunt dezactivate. admin_help: Deoarece sunteți administrator, puteți utiliza și {commands} pentru a gestiona utilizatorii. +readarr_disabled: Scuze, dar suportul pentru carte este dezactivat. +include_book_title_in_cmd: Vă rugăm să includeți titlul cartii în comandă, de exemplu {commands} +no_matching_books: Îmi pare rău, dar nu am găsit niciun carte cu titlu acesta. +help_readarr: Foloseste {book_commands} pentru a adăuga un carte la Readarr. +no_metadata_profiles: "Eroare la adăugare {kind}: nu sunt activate profiluri de metadate pentru {app}! Verificați configurația Searcharr și încercați din nou." +add_metadata_button: "Adăugați metadate: {metadata}" \ No newline at end of file diff --git a/lang/ru-ru.yml b/lang/ru-ru.yml index 1bbb91d..3bc61c5 100644 --- a/lang/ru-ru.yml +++ b/lang/ru-ru.yml @@ -4,6 +4,7 @@ language_label: Русский movie: фильм series: сериал season: сезон +book: книга title: название title_here: название сюда password: пароль @@ -25,6 +26,7 @@ convo_not_found: Я получил вашу команду, но я не узн search_canceled: Поиск отменён! no_root_folders: "Ошибка при добавлении {kind}: нет включенных корневых папок для {app}! Пожалуйста, проверьте вашу конфигурацию Searcharr и попробуйте еще раз." no_quality_profiles: "Ошибка при добавлении {kind}: нет включенных профилей качества для {app}! Пожалуйста, проверьте вашу конфигурацию Searcharr и попробуйте еще раз." +no_language_profiles: "Ошибка при добавлении {kind}: для {app} не включены языковые профили! Проверьте настройки Searcharr и повторите попытку." all_seasons: Все сезоны first_season: Только первый сезон latest_season: Только последний сезон @@ -42,6 +44,7 @@ add_tag_button: "Добавить тэг: {tag}" finished_tagging_button: Закончить тэггирование monitor_button: Отслеживать {option} add_quality_button: "Добавить качество: {quality}" +add_language_button: "Добавить язык: {language}" add_path_button: Добавить в {path} add_button: Добавить {kind}! already_added_button: Уже добавлен! @@ -57,3 +60,9 @@ help_sonarr: Используйте {series_commands} для добавлени help_radarr: Используйте {movie_commands} для добавления фильмов в Radarr. no_features: Извините, но все мои функции, на данный момент, отключены. admin_help: У вас есть права администратора, поэтому вы можете использовать {commands} для управления пользователям. +readarr_disabled: Извините, но поддержка книг отключена. +include_book_title_in_cmd: Добавьте в команду название книги, например {commands} +no_matching_books: Извините, но я не смог найти подходящие книги. +help_readarr: Используйте {book_commands}, для добавления сериалов в Readarr. +no_metadata_profiles: "Ошибка при добавлении {kind}: профили метаданных не включены для {app}! Пожалуйста, проверьте настройки Searcharr и повторите попытку." +add_metadata_button: "Добавить метаданные: {metadata}" \ No newline at end of file diff --git a/radarr.py b/radarr.py index 35df10c..223d4b4 100644 --- a/radarr.py +++ b/radarr.py @@ -156,18 +156,24 @@ def get_all_tags(self): self.logger.debug(f"Result of API call to get all tags: {r}") return [] if not r else r - def get_filtered_tags(self, allowed_tags): + def get_filtered_tags(self, allowed_tags, excluded_tags): r = self.get_all_tags() if not r: return [] elif allowed_tags == []: - return [x for x in r if not x["label"].startswith("searcharr-")] + return [ + x + for x in r + if not x["label"].startswith("searcharr-") + and not x["label"] in excluded_tags + ] else: return [ x for x in r if not x["label"].startswith("searcharr-") and (x["label"] in allowed_tags or x["id"] in allowed_tags) + and x["label"] not in excluded_tags ] def add_tag(self, tag): diff --git a/readarr.py b/readarr.py index 040726d..c2e6e44 100644 --- a/readarr.py +++ b/readarr.py @@ -53,9 +53,7 @@ def discover_version(self, api_url, api_key): return None def lookup_book(self, title): - r = self._api_get( - "search", {"term": quote(title)} - ) + r = self._api_get("search", {"term": quote(title)}) if not r: return [] @@ -81,7 +79,8 @@ def lookup_book(self, title): "author": x.get("book").get("author"), "editions": x.get("book").get("editions"), } - for x in r if x.get("book") + for x in r + if x.get("book") ] def add_book( @@ -95,7 +94,7 @@ def add_book( return False if not book_info: - book_info = self.lookup_book(book_info['title']) + book_info = self.lookup_book(book_info["title"]) if len(book_info): book_info = book_info[0] else: @@ -127,7 +126,7 @@ def add_book( "foreignAuthorId": book_info["author"]["foreignAuthorId"], "rootFolderPath": path, "tags": tag_ids, - } + }, } return self._api_post("book", params) @@ -164,18 +163,24 @@ def get_all_tags(self): self.logger.debug(f"Result of API call to get all tags: {r}") return [] if not r else r - def get_filtered_tags(self, allowed_tags): + def get_filtered_tags(self, allowed_tags, excluded_tags): r = self.get_all_tags() if not r: return [] elif allowed_tags == []: - return [x for x in r if not x["label"].startswith("searcharr-")] + return [ + x + for x in r + if not x["label"].startswith("searcharr-") + and not x["label"] in excluded_tags + ] else: return [ x for x in r if not x["label"].startswith("searcharr-") and (x["label"] in allowed_tags or x["id"] in allowed_tags) + and x["label"] not in excluded_tags ] def add_tag(self, tag): @@ -223,9 +228,7 @@ def lookup_quality_profile(self, v): ) def get_all_quality_profiles(self): - return ( - self._api_get("qualityProfile", {}) - ) or None + return (self._api_get("qualityProfile", {})) or None def lookup_metadata_profile(self, v): # Look up metadata profile from a profile name or id @@ -235,9 +238,7 @@ def lookup_metadata_profile(self, v): ) def get_all_metadata_profiles(self): - return ( - self._api_get("metadataprofile", {}) - ) or None + return (self._api_get("metadataprofile", {})) or None def lookup_root_folder(self, v): # Look up root folder from a path or id diff --git a/searcharr.py b/searcharr.py index 6185683..9b4d64f 100644 --- a/searcharr.py +++ b/searcharr.py @@ -12,7 +12,7 @@ from threading import Lock from urllib.parse import parse_qsl import uuid -import arrow +from datetime import datetime from telegram import InlineKeyboardButton, InlineKeyboardMarkup, InputMediaPhoto from telegram.error import BadRequest @@ -24,7 +24,7 @@ import readarr import settings -__version__ = "2.2" +__version__ = "3.0" DBPATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data") DBFILE = "searcharr.db" @@ -101,6 +101,11 @@ def __init__(self, token): self.sonarr._quality_profiles = quality_profiles language_profiles = [] + if not hasattr(settings, "sonarr_language_profile_id"): + settings.sonarr_language_profile_id = [] + logger.warning( + 'No sonarr_language_profile_id setting detected. Please set one in settings.py if needed (e.g. sonarr_language_profile_id=["English", "German"]).' + ) if not isinstance(settings.sonarr_language_profile_id, list): settings.sonarr_language_profile_id = [ settings.sonarr_language_profile_id @@ -127,8 +132,6 @@ def __init__(self, token): ) self.sonarr._language_profiles = language_profiles - - root_folders = [] if not hasattr(settings, "sonarr_series_paths"): settings.sonarr_series_paths = [] @@ -288,11 +291,19 @@ def __init__(self, token): for t in settings.radarr_forced_tags: if t_id := self.radarr.get_tag_id(t): logger.debug(f"Tag id [{t_id}] for forced Radarr tag [{t}]") - self.readarr = ( - readarr.Readarr(settings.readarr_url, settings.readarr_api_key, args.verbose) - if settings.readarr_enabled - else None - ) + if hasattr(settings, "readarr_enabled"): + self.readarr = ( + readarr.Readarr( + settings.readarr_url, settings.readarr_api_key, args.verbose + ) + if settings.readarr_enabled + else None + ) + else: + self.readarr = None + logger.warning( + "No readarr_enabled setting found. If you want Searcharr to support Readarr, please refer to the sample settings on github and add settings for Readarr to settings.py." + ) if self.readarr: quality_profiles = [] if not isinstance(settings.readarr_quality_profile_id, list): @@ -301,23 +312,23 @@ def __init__(self, token): ] for i in settings.readarr_quality_profile_id: logger.debug( - f"Looking up/validating readarr quality profile id for [{i}]..." + f"Looking up/validating Readarr quality profile id for [{i}]..." ) foundProfile = self.readarr.lookup_quality_profile(i) if not foundProfile: - logger.error(f"readarr quality profile id/name [{i}] is invalid!") + logger.error(f"Readarr quality profile id/name [{i}] is invalid!") else: logger.debug( - f"Found readarr quality profile for [{i}]: [{foundProfile}]" + f"Found Readarr quality profile for [{i}]: [{foundProfile}]" ) quality_profiles.append(foundProfile) if not len(quality_profiles): logger.warning( - f"No valid readarr quality profile(s) provided! Using all of the quality profiles I found in readarr: {self.readarr._quality_profiles}" + f"No valid Readarr quality profile(s) provided! Using all of the quality profiles I found in Readarr: {self.readarr._quality_profiles}" ) else: logger.debug( - f"Using the following readarr quality profile(s): {[(x['id'], x['name']) for x in quality_profiles]}" + f"Using the following Readarr quality profile(s): {[(x['id'], x['name']) for x in quality_profiles]}" ) self.readarr._quality_profiles = quality_profiles metadata_profiles = [] @@ -327,23 +338,23 @@ def __init__(self, token): ] for i in settings.readarr_metadata_profile_id: logger.debug( - f"Looking up/validating readarr metadata profile id for [{i}]..." + f"Looking up/validating Readarr metadata profile id for [{i}]..." ) foundProfile = self.readarr.lookup_metadata_profile(i) if not foundProfile: - logger.error(f"readarr metadata profile id/name [{i}] is invalid!") + logger.error(f"Readarr metadata profile id/name [{i}] is invalid!") else: logger.debug( - f"Found readarr metadata profile for [{i}]: [{foundProfile}]" + f"Found Readarr metadata profile for [{i}]: [{foundProfile}]" ) metadata_profiles.append(foundProfile) if not len(metadata_profiles): logger.warning( - f"No valid readarr metadata profile(s) provided! Using all of the metadata profiles I found in readarr: {self.readarr._metadata_profiles}" + f"No valid Readarr metadata profile(s) provided! Using all of the metadata profiles I found in Readarr: {self.readarr._metadata_profiles}" ) else: logger.debug( - f"Using the following readarr metadata profile(s): {[(x['id'], x['name']) for x in metadata_profiles]}" + f"Using the following Readarr metadata profile(s): {[(x['id'], x['name']) for x in metadata_profiles]}" ) self.readarr._metadata_profiles = metadata_profiles @@ -351,25 +362,25 @@ def __init__(self, token): if not hasattr(settings, "readarr_book_paths"): settings.readarr_book_paths = [] logger.warning( - 'No readarr_movie_paths setting detected. Please set one in settings.py (readarr_movie_paths=["/path/1", "/path/2"]). Proceeding with all root folders configured in readarr.' + 'No readarr_book_paths setting detected. Please set one in settings.py (readarr_book_paths=["/path/1", "/path/2"]). Proceeding with all root folders configured in Readarr.' ) if not isinstance(settings.readarr_book_paths, list): settings.readarr_book_paths = [settings.readarr_book_paths] for i in settings.readarr_book_paths: - logger.debug(f"Looking up/validating readarr root folder for [{i}]...") + logger.debug(f"Looking up/validating Readarr root folder for [{i}]...") foundPath = self.readarr.lookup_root_folder(i) if not foundPath: - logger.error(f"readarr root folder path/id [{i}] is invalid!") + logger.error(f"Readarr root folder path/id [{i}] is invalid!") else: - logger.debug(f"Found readarr root folder for [{i}]: [{foundPath}]") + logger.debug(f"Found Readarr root folder for [{i}]: [{foundPath}]") root_folders.append(foundPath) if not len(root_folders): logger.warning( - f"No valid readarr root folder(s) provided! Using all of the root folders I found in readarr: {self.readarr._root_folders}" + f"No valid Readarr root folder(s) provided! Using all of the root folders I found in Readarr: {self.readarr._root_folders}" ) else: logger.debug( - f"Using the following readarr root folder(s): {[(x['id'], x['path']) for x in root_folders]}" + f"Using the following Readarr root folder(s): {[(x['id'], x['path']) for x in root_folders]}" ) self.readarr._root_folders = root_folders if not hasattr(settings, "readarr_tag_with_username"): @@ -380,17 +391,17 @@ def __init__(self, token): if not hasattr(settings, "readarr_movie_command_aliases"): settings.readarr_book_command_aliases = ["book"] logger.warning( - 'No readarr_book_command_aliases setting found. Please add readarr_movie_command_aliases to settings.py (e.g. readarr_book_command_aliases=["book", "bk"]. Defaulting to ["book"].' + 'No readarr_book_command_aliases setting found. Please add readarr_book_command_aliases to settings.py (e.g. readarr_book_command_aliases=["book", "bk"]. Defaulting to ["book"].' ) if not hasattr(settings, "readarr_forced_tags"): settings.readarr_forced_tags = [] logger.warning( - 'No readarr_forced_tags setting found. Please add readarr_forced_tags to settings.py (e.g. readarr_forced_tags=["tag-1", "tag-2"]) if you want specific tags added to each movie. Defaulting to empty list ([]).' + 'No readarr_forced_tags setting found. Please add readarr_forced_tags to settings.py (e.g. readarr_forced_tags=["tag-1", "tag-2"]) if you want specific tags added to each book. Defaulting to empty list ([]).' ) if not hasattr(settings, "readarr_allow_user_to_select_tags"): settings.readarr_allow_user_to_select_tags = True logger.warning( - "No readarr_allow_user_to_select_tags setting found. Please add readarr_allow_user_to_select_tags to settings.py (e.g. readarr_allow_user_to_select_tags=False) if you do not want users to be able to select tags when adding a movie. Defaulting to True." + "No readarr_allow_user_to_select_tags setting found. Please add readarr_allow_user_to_select_tags to settings.py (e.g. readarr_allow_user_to_select_tags=False) if you do not want users to be able to select tags when adding a book. Defaulting to True." ) if not hasattr(settings, "readarr_user_selectable_tags"): settings.readarr_user_selectable_tags = [] @@ -400,11 +411,11 @@ def __init__(self, token): for t in settings.readarr_user_selectable_tags: if t_id := self.readarr.get_tag_id(t): logger.debug( - f"Tag id [{t_id}] for user-selectable readarr tag [{t}]" + f"Tag id [{t_id}] for user-selectable Readarr tag [{t}]" ) for t in settings.readarr_forced_tags: if t_id := self.readarr.get_tag_id(t): - logger.debug(f"Tag id [{t_id}] for forced readarr tag [{t}]") + logger.debug(f"Tag id [{t_id}] for forced Readarr tag [{t}]") self.conversations = {} if not hasattr(settings, "searcharr_admin_password"): @@ -414,7 +425,7 @@ def __init__(self, token): ) if settings.searcharr_password == "": logger.warning( - 'Password is blank. This will allow anyone to add series/movies using your bot. If this is unexpected, set a password in settings.py (searcharr_password="your password").' + 'Password is blank. This will allow anyone to add series/movies/books using your bot. If this is unexpected, set a password in settings.py (searcharr_password="your password").' ) if not hasattr(settings, "searcharr_start_command_aliases"): settings.searcharr_start_command_aliases = ["start"] @@ -958,7 +969,13 @@ def callback(self, update, context): self._xlate( "no_root_folders", kind=self._xlate(convo["type"]), - app="Sonarr" if convo["type"] == "series" else "Radarr" if convo['type'] == 'movie' else 'Readarr', + app="Sonarr" + if convo["type"] == "series" + else "Radarr" + if convo["type"] == "movie" + else "Readarr" + if convo["type"] == "book" + else "???", ) ) query.message.delete() @@ -1042,14 +1059,18 @@ def callback(self, update, context): self._xlate( "no_quality_profiles", kind=self._xlate(convo["type"]), - app="Sonarr" if convo["type"] == "series" else "Radarr" if convo['type'] == 'movie' else 'Readarr', + app="Sonarr" + if convo["type"] == "series" + else "Radarr" + if convo["type"] == "movie" + else "Readarr", ) ) query.message.delete() query.answer() return - if convo['type'] == 'book' and not additional_data.get("m"): + if convo["type"] == "book" and not additional_data.get("m"): metadata_profiles = self.readarr._metadata_profiles if len(metadata_profiles) > 1: # prepare response to prompt user to select quality profile, and return @@ -1099,7 +1120,11 @@ def callback(self, update, context): self._xlate( "no_metadata_profiles", kind=self._xlate(convo["type"]), - app="Sonarr" if convo["type"] == "series" else "Radarr" if convo['type'] == 'movie' else 'Readarr', + app="Sonarr" + if convo["type"] == "series" + else "Radarr" + if convo["type"] == "movie" + else "Readarr", ) ) query.message.delete() @@ -1107,66 +1132,63 @@ def callback(self, update, context): return if not additional_data.get("l"): - language_profiles = ( - self.sonarr._language_profiles - if convo["type"] == "series" - else self.radarr._language_profiles - ) - if len(language_profiles) > 1: - # prepare response to prompt user to select language profile, and return - reply_message, reply_markup = self._prepare_response( - convo["type"], - r, - cid, - i, - len(convo["results"]), - add=True, - language_profiles=language_profiles, - ) - try: - query.message.edit_media( - media=InputMediaPhoto(r["remotePoster"]), - reply_markup=reply_markup, + if convo["type"] == "series": + language_profiles = self.sonarr._language_profiles + + if len(language_profiles) > 1: + # prepare response to prompt user to select language profile, and return + reply_message, reply_markup = self._prepare_response( + convo["type"], + r, + cid, + i, + len(convo["results"]), + add=True, + language_profiles=language_profiles, ) - except BadRequest as e: - if str(e) in self._bad_request_poster_error_messages: - logger.error( - f"Error sending photo [{r['remotePoster']}]: BadRequest: {e}. Attempting to send with default poster..." - ) + try: query.message.edit_media( - media=InputMediaPhoto( - "https://artworks.thetvdb.com/banners/images/missing/movie.jpg" - ), + media=InputMediaPhoto(r["remotePoster"]), reply_markup=reply_markup, ) - else: - raise - query.bot.edit_message_caption( - chat_id=query.message.chat_id, - message_id=query.message.message_id, - caption=reply_message, - reply_markup=reply_markup, - ) - query.answer() - return - elif len(language_profiles) == 1: - logger.debug( - f"Only one language profile enabled. Adding/Updating additional data for cid=[{cid}], key=[q], value=[{language_profiles[0]['id']}]..." - ) - self._update_add_data(cid, "l", language_profiles[0]["id"]) - else: - self._delete_conversation(cid) - query.message.reply_text( - self._xlate( - "no_language_profiles", - kind=self._xlate(convo["type"]), - app="Sonarr" if convo["type"] == "series" else "Radarr", + except BadRequest as e: + if str(e) in self._bad_request_poster_error_messages: + logger.error( + f"Error sending photo [{r['remotePoster']}]: BadRequest: {e}. Attempting to send with default poster..." + ) + query.message.edit_media( + media=InputMediaPhoto( + "https://artworks.thetvdb.com/banners/images/missing/movie.jpg" + ), + reply_markup=reply_markup, + ) + else: + raise + query.bot.edit_message_caption( + chat_id=query.message.chat_id, + message_id=query.message.message_id, + caption=reply_message, + reply_markup=reply_markup, ) - ) - query.message.delete() - query.answer() - return - + query.answer() + return + elif len(language_profiles) == 1: + logger.debug( + f"Only one language profile enabled. Adding/Updating additional data for cid=[{cid}], key=[q], value=[{language_profiles[0]['id']}]..." + ) + self._update_add_data(cid, "l", language_profiles[0]["id"]) + else: + self._delete_conversation(cid) + query.message.reply_text( + self._xlate( + "no_language_profiles", + kind=self._xlate(convo["type"]), + app="Sonarr" if convo["type"] == "series" else "Radarr", + ) + ) + query.message.delete() + query.answer() + return if ( convo["type"] == "series" @@ -1218,26 +1240,29 @@ def callback(self, update, context): if convo["type"] == "series": all_tags = self.sonarr.get_filtered_tags( - settings.sonarr_user_selectable_tags + settings.sonarr_user_selectable_tags, + settings.sonarr_forced_tags, ) allow_user_to_select_tags = settings.sonarr_allow_user_to_select_tags forced_tags = settings.sonarr_forced_tags elif convo["type"] == "movie": all_tags = self.radarr.get_filtered_tags( - settings.radarr_user_selectable_tags + settings.radarr_user_selectable_tags, + settings.radarr_forced_tags, ) allow_user_to_select_tags = settings.radarr_allow_user_to_select_tags forced_tags = settings.radarr_forced_tags elif convo["type"] == "book": all_tags = self.readarr.get_filtered_tags( - settings.readarr_user_selectable_tags + settings.readarr_user_selectable_tags, + settings.readarr_forced_tags, ) allow_user_to_select_tags = settings.readarr_allow_user_to_select_tags forced_tags = settings.readarr_forced_tags if allow_user_to_select_tags and not additional_data.get("td"): if not len(all_tags): logger.warning( - f"User tagging is enabled, but no tags found. Make sure there are tags in {'Sonarr' if convo['type'] == 'series' else 'Radarr' if convo['type'] == 'movie' else 'Readarr'} matching your Searcharr configuration." + f"User tagging is enabled, but no tags found. Make sure there are tags{' in Sonarr' if convo['type'] == 'series' else ' in Radarr' if convo['type'] == 'movie' else ' in Readarr' if convo['type'] == 'book' else ''} matching your Searcharr configuration." ) elif not additional_data.get("tt"): reply_message, reply_markup = self._prepare_response( @@ -1537,9 +1562,7 @@ def _prepare_response( elif kind == "book" and r["links"]: for link in r["links"]: keyboardNavRow.append( - InlineKeyboardButton( - link["name"], url=link["url"] - ) + InlineKeyboardButton(link["name"], url=link["url"]) ) if kind == "series" or kind == "movie": if r["imdbId"]: @@ -1596,13 +1619,12 @@ def _prepare_response( ], ) elif language_profiles: - for l in language_profiles: + for lp in language_profiles: keyboard.append( [ InlineKeyboardButton( - self._xlate("select_language_button", language=l["name"]), - callback_data=f"{cid}^^^{i}^^^add^^l={l['id']}", - + self._xlate("add_language_button", language=lp["name"]), + callback_data=f"{cid}^^^{i}^^^add^^l={lp['id']}", ) ], ) @@ -1672,8 +1694,13 @@ def _prepare_response( 0:1024 ] elif kind == "book": - release = arrow.get(r["releaseDate"]) - reply_message = f"{r['title']}{' - ' + r['disambiguation'] if r['disambiguation'] else ''}{' - ' + r['seriesTitle'] if r['seriesTitle'] else ''} ({release.format('MMMM DD, YYYY')})\n\n{r['overview']}"[ + try: + release = datetime.strptime( + r["releaseDate"], "%Y-%m-%dT%H:%M:%SZ" + ).strftime("%b %d, %Y") + except (ValueError, TypeError): + release = "???" + reply_message = f"{r['author']['authorName']} - {r['title']}{' - ' + r['disambiguation'] if r['disambiguation'] else ''}{' - ' + r['seriesTitle'] if r['seriesTitle'] else ''} ({release})\n\n{r['overview']}"[ 0:1024 ] else: @@ -1781,14 +1808,18 @@ def cmd_help(self, update, context): ] ), ) - if settings.sonarr_enabled and settings.radarr_enabled and settings.readarr_enabled: - resp = f"{sonarr_help} {radarr_help} {readarr_help}" - elif settings.sonarr_enabled: - resp = sonarr_help - elif settings.radarr_enabled: - resp = radarr_help - elif settings.readarr_enabled: - resp = readarr_help + if ( + settings.sonarr_enabled + or settings.radarr_enabled + or settings.readarr_enabled + ): + resp = "" + if settings.sonarr_enabled: + resp += f" {sonarr_help}" + if settings.radarr_enabled: + resp = f" {radarr_help}" + if settings.readarr_enabled: + resp = f" {readarr_help}" else: resp = self._xlate("no_features") @@ -1822,9 +1853,10 @@ def run(self): for c in settings.searcharr_start_command_aliases: logger.debug(f"Registering [/{c}] as a start command") updater.dispatcher.add_handler(CommandHandler(c, self.cmd_start)) - for c in settings.readarr_book_command_aliases: - logger.debug(f"Registering [/{c}] as a book command") - updater.dispatcher.add_handler(CommandHandler(c, self.cmd_book)) + if self.readarr: + for c in settings.readarr_book_command_aliases: + logger.debug(f"Registering [/{c}] as a book command") + updater.dispatcher.add_handler(CommandHandler(c, self.cmd_book)) for c in settings.radarr_movie_command_aliases: logger.debug(f"Registering [/{c}] as a movie command") updater.dispatcher.add_handler(CommandHandler(c, self.cmd_movie)) diff --git a/settings-sample.py b/settings-sample.py index fa5e955..1f358de 100644 --- a/settings-sample.py +++ b/settings-sample.py @@ -21,7 +21,7 @@ sonarr_url = "" # http://192.168.0.100:8989 sonarr_api_key = "" sonarr_quality_profile_id = ["HD - 720p/1080p"] # can be name or id value - include multiple to allow the user to choose -sonarr_language_profile_id = ["English", "German"] # select languages, include multiple to allow the user to choose +sonarr_language_profile_id = [] # e.g. ["English", "German"] - language profile names/ids from Sonarr, include multiple to allow the user to choose sonarr_add_monitored = True sonarr_search_on_add = True sonarr_tag_with_username = True diff --git a/sonarr.py b/sonarr.py index 15aee73..7017ec6 100644 --- a/sonarr.py +++ b/sonarr.py @@ -28,8 +28,7 @@ def __init__(self, api_url, api_key, verbose=False): self._root_folders = self.get_root_folders() self._all_series = {} self.get_all_series() - - + def lookup_series(self, title=None, tvdb_id=None): r = self._api_get( "series/lookup", {"term": f"tvdb:{tvdb_id}" if tvdb_id else quote(title)} @@ -172,18 +171,24 @@ def get_all_tags(self): self.logger.debug(f"Result of API call to get all tags: {r}") return [] if not r else r - def get_filtered_tags(self, allowed_tags): + def get_filtered_tags(self, allowed_tags, excluded_tags): r = self.get_all_tags() if not r: return [] elif allowed_tags == []: - return [x for x in r if not x["label"].startswith("searcharr-")] + return [ + x + for x in r + if not x["label"].startswith("searcharr-") + and not x["label"] in excluded_tags + ] else: return [ x for x in r if not x["label"].startswith("searcharr-") and (x["label"] in allowed_tags or x["id"] in allowed_tags) + and x["label"] not in excluded_tags ] def add_tag(self, tag): @@ -231,7 +236,7 @@ def lookup_quality_profile(self, v): ) def lookup_language_profile(self, v): - # Look up language profile from a profile name or id + # Look up language profile from a profile name or id return next( (x for x in self._language_profiles if str(v) in [x["name"], str(x["id"])]), None, From 7531c36070ac795161cccca9bad8f821d2fc5309 Mon Sep 17 00:00:00 2001 From: todd <toddrob@gmail.com> Date: Thu, 13 Oct 2022 19:44:47 -0400 Subject: [PATCH 06/16] fix: help command response --- searcharr.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/searcharr.py b/searcharr.py index 9b4d64f..06f2bed 100644 --- a/searcharr.py +++ b/searcharr.py @@ -1817,9 +1817,9 @@ def cmd_help(self, update, context): if settings.sonarr_enabled: resp += f" {sonarr_help}" if settings.radarr_enabled: - resp = f" {radarr_help}" + resp += f" {radarr_help}" if settings.readarr_enabled: - resp = f" {readarr_help}" + resp += f" {readarr_help}" else: resp = self._xlate("no_features") From d951c831a4d88295b8279c4b5d7b616cb63e7119 Mon Sep 17 00:00:00 2001 From: todd <toddrob@gmail.com> Date: Thu, 13 Oct 2022 19:58:36 -0400 Subject: [PATCH 07/16] fix: help crashes if readarr settings are missing --- searcharr.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/searcharr.py b/searcharr.py index 06f2bed..615cfc7 100644 --- a/searcharr.py +++ b/searcharr.py @@ -300,6 +300,7 @@ def __init__(self, token): else None ) else: + settings.readarr_enabled = False self.readarr = None logger.warning( "No readarr_enabled setting found. If you want Searcharr to support Readarr, please refer to the sample settings on github and add settings for Readarr to settings.py." @@ -1799,15 +1800,17 @@ def cmd_help(self, update, context): ] ), ) - readarr_help = self._xlate( - "help_readarr", - book_commands=" OR ".join( - [ - f"`/{c} {self._xlate('title').title()}`" - for c in settings.readarr_book_command_aliases - ] - ), - ) + if self.readarr: + readarr_help = self._xlate( + "help_readarr", + book_commands=" OR ".join( + [ + f"`/{c} {self._xlate('title').title()}`" + for c in settings.readarr_book_command_aliases + ] + ), + ) + if ( settings.sonarr_enabled or settings.radarr_enabled From cc7f1cc6f3d9754f6a528239be06942e1500312f Mon Sep 17 00:00:00 2001 From: todd <toddrob@gmail.com> Date: Fri, 14 Oct 2022 12:04:15 -0400 Subject: [PATCH 08/16] romanian grammar adjustments --- lang/ro.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lang/ro.yml b/lang/ro.yml index 093c7ea..00520a4 100644 --- a/lang/ro.yml +++ b/lang/ro.yml @@ -60,9 +60,9 @@ help_sonarr: Foloseste {series_commands} pentru a adăuga un serial la Sonarr. help_radarr: Foloseste {movie_commands} pentru a adăuga un film la Radarr. no_features: Ne pare rău, dar toate funcțiile mele sunt dezactivate. admin_help: Deoarece sunteți administrator, puteți utiliza și {commands} pentru a gestiona utilizatorii. -readarr_disabled: Scuze, dar suportul pentru carte este dezactivat. +readarr_disabled: Scuze, dar suportul pentru carti este dezactivat. include_book_title_in_cmd: Vă rugăm să includeți titlul cartii în comandă, de exemplu {commands} -no_matching_books: Îmi pare rău, dar nu am găsit niciun carte cu titlu acesta. +no_matching_books: Îmi pare rău, dar nu am găsit nici o carte cu titlu acesta. help_readarr: Foloseste {book_commands} pentru a adăuga un carte la Readarr. no_metadata_profiles: "Eroare la adăugare {kind}: nu sunt activate profiluri de metadate pentru {app}! Verificați configurația Searcharr și încercați din nou." add_metadata_button: "Adăugați metadate: {metadata}" \ No newline at end of file From 611616c9251a589d3af7c55dd9162d069599ee1b Mon Sep 17 00:00:00 2001 From: Ayman Bagabas <ayman.bagabas@gmail.com> Date: Fri, 14 Oct 2022 12:20:50 -0400 Subject: [PATCH 09/16] fix(readarr): forcibly search book --- readarr.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/readarr.py b/readarr.py index c2e6e44..6bd0eb5 100644 --- a/readarr.py +++ b/readarr.py @@ -129,7 +129,12 @@ def add_book( }, } - return self._api_post("book", params) + rsp = self._api_post("book", params) + if rsp is not None and search: + # Force book search + srsp = self._api_post("command", {"name": "BookSearch", "bookIds": [rsp.get("id")]}) + self.logger.debug(f"Result of attempt to search book: {srsp}") + return rsp def get_root_folders(self): r = self._api_get("rootfolder", {}) From e448eac27960179553c90c40693e749ddb7b6dc5 Mon Sep 17 00:00:00 2001 From: todd <toddrob@gmail.com> Date: Sat, 22 Oct 2022 12:42:01 -0400 Subject: [PATCH 10/16] code formatting w/ black --- readarr.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/readarr.py b/readarr.py index 6bd0eb5..82bc5db 100644 --- a/readarr.py +++ b/readarr.py @@ -132,7 +132,9 @@ def add_book( rsp = self._api_post("book", params) if rsp is not None and search: # Force book search - srsp = self._api_post("command", {"name": "BookSearch", "bookIds": [rsp.get("id")]}) + srsp = self._api_post( + "command", {"name": "BookSearch", "bookIds": [rsp.get("id")]} + ) self.logger.debug(f"Result of attempt to search book: {srsp}") return rsp From c19f23d8bcb1f12a2a07de53cc637779646e2eaf Mon Sep 17 00:00:00 2001 From: todd <toddrob@gmail.com> Date: Sat, 22 Oct 2022 12:48:35 -0400 Subject: [PATCH 11/16] chore(ci): update docker action components --- .github/workflows/beta.yml | 8 ++++---- .github/workflows/pre-release.yml | 8 ++++---- .github/workflows/release.yml | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml index e1b5b39..a4a1eb0 100644 --- a/.github/workflows/beta.yml +++ b/.github/workflows/beta.yml @@ -11,19 +11,19 @@ jobs: steps: - name: Set up QEMU - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v2 - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v3 with: platforms: linux/amd64,linux/arm64,linux/arm/v7 push: true diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index df4355f..7adc81e 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -10,23 +10,23 @@ jobs: steps: - name: Set up QEMU - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v2 - name: Get Version id: get_version uses: battila7/get-version-action@v2 - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v3 with: platforms: linux/amd64,linux/arm64,linux/arm/v7 push: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index df963e2..02a4716 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,23 +10,23 @@ jobs: steps: - name: Set up QEMU - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v2 - name: Get Version id: get_version uses: battila7/get-version-action@v2 - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v3 with: platforms: linux/amd64,linux/arm64,linux/arm/v7 push: true From b7495e85a46b3a5eaf4c01fb0c94e0b08d1b616f Mon Sep 17 00:00:00 2001 From: todd <toddrob@gmail.com> Date: Mon, 31 Oct 2022 12:56:33 -0400 Subject: [PATCH 12/16] fix: prevent double search when adding book --- readarr.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/readarr.py b/readarr.py index 82bc5db..d0dc48c 100644 --- a/readarr.py +++ b/readarr.py @@ -118,7 +118,9 @@ def add_book( "titleSlug": book_info["titleSlug"], "monitored": monitored, "anyEditionOk": True, - "addOptions": {"searchForNewBook": search}, + "addOptions": { + "searchForNewBook": False # manually searching below instead + }, "editions": book_info["editions"], "author": { "qualityProfileId": quality, From da76cbdc0641523d041cf824a96b67e979302eda Mon Sep 17 00:00:00 2001 From: vinimk <vinekoetz@gmail.com> Date: Thu, 8 Dec 2022 23:50:55 +0100 Subject: [PATCH 13/16] Updated to sonarr api v3 in order to work in sonarr 4.x --- sonarr.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sonarr.py b/sonarr.py index 909d245..5a0398e 100644 --- a/sonarr.py +++ b/sonarr.py @@ -22,7 +22,7 @@ def __init__(self, api_url, api_key, verbose=False): self.logger.error( "Invalid Sonarr URL detected. Please update your settings to include http:// or https:// on the beginning of the URL." ) - self.api_url = api_url + "/api/{endpoint}?apikey=" + api_key + self.api_url = api_url + "/api/v3/{endpoint}?apikey=" + api_key self._quality_profiles = self.get_all_quality_profiles() self._root_folders = self.get_root_folders() self._all_series = {} @@ -38,7 +38,7 @@ def lookup_series(self, title=None, tvdb_id=None): return [ { "title": x.get("title"), - "seasonCount": x.get("seasonCount", 0), + "seasonCount": len(x.get("seasons")), "status": x.get("status", "Unknown Status"), "overview": x.get("overview", "Overview not available."), "network": x.get("network"), @@ -141,7 +141,7 @@ def add_series( "tags": tag_ids, "addOptions": { "ignoreEpisodesWithFiles": unmonitor_existing, - "ignoreEpisodesWithoutFiles": "false", + "ignoreEpisodesWithoutFiles": False, "searchForMissingEpisodes": search, }, } @@ -227,7 +227,7 @@ def lookup_quality_profile(self, v): ) def get_all_quality_profiles(self): - return self._api_get("profile", {}) or None + return self._api_get("qualityprofile", {}) or None def lookup_root_folder(self, v): # Look up root folder from a path or id From 0cb2433d6699646ef985d45e19998ff95797df3b Mon Sep 17 00:00:00 2001 From: todd <toddrob@gmail.com> Date: Wed, 14 Dec 2022 21:14:45 -0500 Subject: [PATCH 14/16] fix: patch malformed api url for language profile --- sonarr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonarr.py b/sonarr.py index 3b94bfb..88aacfa 100644 --- a/sonarr.py +++ b/sonarr.py @@ -246,7 +246,7 @@ def get_all_quality_profiles(self): return self._api_get("qualityprofile", {}) or None def get_all_language_profiles(self): - return self._api_get("v3/languageprofile", {}) or None + return self._api_get("languageprofile", {}) or None def lookup_root_folder(self, v): # Look up root folder from a path or id From 935ebc5400e1d6a8b0f47801d0722186a5e9ab2d Mon Sep 17 00:00:00 2001 From: todd <toddrob@gmail.com> Date: Wed, 14 Dec 2022 21:26:00 -0500 Subject: [PATCH 15/16] Remove Sonarr language profile support --- searcharr.py | 102 --------------------------------------------- settings-sample.py | 1 - sonarr.py | 13 ------ 3 files changed, 116 deletions(-) diff --git a/searcharr.py b/searcharr.py index 615cfc7..ff48302 100644 --- a/searcharr.py +++ b/searcharr.py @@ -100,38 +100,6 @@ def __init__(self, token): ) self.sonarr._quality_profiles = quality_profiles - language_profiles = [] - if not hasattr(settings, "sonarr_language_profile_id"): - settings.sonarr_language_profile_id = [] - logger.warning( - 'No sonarr_language_profile_id setting detected. Please set one in settings.py if needed (e.g. sonarr_language_profile_id=["English", "German"]).' - ) - if not isinstance(settings.sonarr_language_profile_id, list): - settings.sonarr_language_profile_id = [ - settings.sonarr_language_profile_id - ] - for i in settings.sonarr_language_profile_id: - logger.debug( - f"Looking up/validating Sonarr language profile id for [{i}]..." - ) - foundProfile = self.sonarr.lookup_language_profile(i) - if not foundProfile: - logger.error(f"Sonarr language profile id/name [{i}] is invalid!") - else: - logger.debug( - f"Found Sonarr language profile for [{i}]: [{foundProfile}]" - ) - language_profiles.append(foundProfile) - if not len(language_profiles): - logger.warning( - f"No valid Sonarr language profile(s) provided! Using all of the language profiles I found in Sonarr: {self.sonarr._language_profiles}" - ) - else: - logger.debug( - f"Using the following Sonarr language profile(s): {[(x['id'], x['name']) for x in language_profiles]}" - ) - self.sonarr._language_profiles = language_profiles - root_folders = [] if not hasattr(settings, "sonarr_series_paths"): settings.sonarr_series_paths = [] @@ -1132,65 +1100,6 @@ def callback(self, update, context): query.answer() return - if not additional_data.get("l"): - if convo["type"] == "series": - language_profiles = self.sonarr._language_profiles - - if len(language_profiles) > 1: - # prepare response to prompt user to select language profile, and return - reply_message, reply_markup = self._prepare_response( - convo["type"], - r, - cid, - i, - len(convo["results"]), - add=True, - language_profiles=language_profiles, - ) - try: - query.message.edit_media( - media=InputMediaPhoto(r["remotePoster"]), - reply_markup=reply_markup, - ) - except BadRequest as e: - if str(e) in self._bad_request_poster_error_messages: - logger.error( - f"Error sending photo [{r['remotePoster']}]: BadRequest: {e}. Attempting to send with default poster..." - ) - query.message.edit_media( - media=InputMediaPhoto( - "https://artworks.thetvdb.com/banners/images/missing/movie.jpg" - ), - reply_markup=reply_markup, - ) - else: - raise - query.bot.edit_message_caption( - chat_id=query.message.chat_id, - message_id=query.message.message_id, - caption=reply_message, - reply_markup=reply_markup, - ) - query.answer() - return - elif len(language_profiles) == 1: - logger.debug( - f"Only one language profile enabled. Adding/Updating additional data for cid=[{cid}], key=[q], value=[{language_profiles[0]['id']}]..." - ) - self._update_add_data(cid, "l", language_profiles[0]["id"]) - else: - self._delete_conversation(cid) - query.message.reply_text( - self._xlate( - "no_language_profiles", - kind=self._xlate(convo["type"]), - app="Sonarr" if convo["type"] == "series" else "Radarr", - ) - ) - query.message.delete() - query.answer() - return - if ( convo["type"] == "series" and settings.sonarr_season_monitor_prompt @@ -1535,7 +1444,6 @@ def _prepare_response( add=False, paths=None, quality_profiles=None, - language_profiles=None, metadata_profiles=None, monitor_options=None, tags=None, @@ -1619,16 +1527,6 @@ def _prepare_response( ) ], ) - elif language_profiles: - for lp in language_profiles: - keyboard.append( - [ - InlineKeyboardButton( - self._xlate("add_language_button", language=lp["name"]), - callback_data=f"{cid}^^^{i}^^^add^^l={lp['id']}", - ) - ], - ) elif metadata_profiles: for m in metadata_profiles: keyboard.append( diff --git a/settings-sample.py b/settings-sample.py index 1f358de..39c573a 100644 --- a/settings-sample.py +++ b/settings-sample.py @@ -21,7 +21,6 @@ sonarr_url = "" # http://192.168.0.100:8989 sonarr_api_key = "" sonarr_quality_profile_id = ["HD - 720p/1080p"] # can be name or id value - include multiple to allow the user to choose -sonarr_language_profile_id = [] # e.g. ["English", "German"] - language profile names/ids from Sonarr, include multiple to allow the user to choose sonarr_add_monitored = True sonarr_search_on_add = True sonarr_tag_with_username = True diff --git a/sonarr.py b/sonarr.py index 88aacfa..6a7f02f 100644 --- a/sonarr.py +++ b/sonarr.py @@ -24,7 +24,6 @@ def __init__(self, api_url, api_key, verbose=False): ) self.api_url = api_url + "/api/v3/{endpoint}?apikey=" + api_key self._quality_profiles = self.get_all_quality_profiles() - self._language_profiles = self.get_all_language_profiles() self._root_folders = self.get_root_folders() self._all_series = {} self.get_all_series() @@ -101,7 +100,6 @@ def add_series( path = additional_data["p"] quality = int(additional_data["q"]) - language = int(additional_data["l"]) monitor_options = int(additional_data.get("m", 0)) if monitor_options == 1: # Monitor only the first season @@ -132,7 +130,6 @@ def add_series( "tvdbId": series_info["tvdbId"], "title": series_info["title"], "qualityProfileId": quality, - "languageProfileId": language, "titleSlug": series_info["titleSlug"], "images": series_info["images"], "seasons": series_info["seasons"], @@ -235,19 +232,9 @@ def lookup_quality_profile(self, v): None, ) - def lookup_language_profile(self, v): - # Look up language profile from a profile name or id - return next( - (x for x in self._language_profiles if str(v) in [x["name"], str(x["id"])]), - None, - ) - def get_all_quality_profiles(self): return self._api_get("qualityprofile", {}) or None - def get_all_language_profiles(self): - return self._api_get("languageprofile", {}) or None - def lookup_root_folder(self, v): # Look up root folder from a path or id return next( From f95f25fea45de6894622bb731183d1eb8991f758 Mon Sep 17 00:00:00 2001 From: todd <toddrob@gmail.com> Date: Fri, 16 Dec 2022 00:08:03 -0500 Subject: [PATCH 16/16] fix: continue using legacy api for Sonarr v3 --- sonarr.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/sonarr.py b/sonarr.py index 6a7f02f..8540f63 100644 --- a/sonarr.py +++ b/sonarr.py @@ -22,12 +22,38 @@ def __init__(self, api_url, api_key, verbose=False): self.logger.error( "Invalid Sonarr URL detected. Please update your settings to include http:// or https:// on the beginning of the URL." ) - self.api_url = api_url + "/api/v3/{endpoint}?apikey=" + api_key + self.sonarr_version = self.discover_version(api_url, api_key) + if not self.sonarr_version.startswith("4."): + self.api_url = api_url + "/api/{endpoint}?apikey=" + api_key self._quality_profiles = self.get_all_quality_profiles() self._root_folders = self.get_root_folders() self._all_series = {} self.get_all_series() + def discover_version(self, api_url, api_key): + try: + self.api_url = api_url + "/api/v3/{endpoint}?apikey=" + api_key + sonarrInfo = self._api_get("system/status") + self.logger.debug( + f"Discovered Sonarr version {sonarrInfo.get('version')} using v3 api." + ) + return sonarrInfo.get("version") + except requests.exceptions.HTTPError as e: + self.logger.debug(f"Sonarr v3 API threw exception: {e}") + + try: + self.api_url = api_url + "/api/{endpoint}?apikey=" + api_key + sonarrInfo = self._api_get("system/status") + self.logger.warning( + f"Discovered Sonarr version {sonarrInfo.get('version')}. Using legacy API. Consider upgrading to the latest version of Radarr for the best experience." + ) + return sonarrInfo.get("version") + except requests.exceptions.HTTPError as e: + self.logger.debug(f"Sonarr legacy API threw exception: {e}") + + self.logger.debug("Failed to discover Sonarr version") + return None + def lookup_series(self, title=None, tvdb_id=None): r = self._api_get( "series/lookup", {"term": f"tvdb:{tvdb_id}" if tvdb_id else quote(title)}