def find_plex_show(self, media_entries: List[MediaEntry]) -> DownloadableQueue: """ Fetches a list of shows by title if they exist, if not an empty collection would be returned :param media_entries: a model consisting of various anime names from anilist :return: a list of optional shows """ shows_in_plex_matching_users_list: List[Optional[Show]] = list() show_media_entry_mapped_to_plex: List[Optional[MediaEntry]] = list() shows_missing_in_plex_found_on_users_list: List[Optional[MediaEntry]] = list() for entry in media_entries: if entry.media.status != 'NOT_YET_RELEASED': if entry.status != 'COMPLETED': show = self.plex_controller.find_all_by_title( entry, lambda: self.__add_missing_item( entry, shows_missing_in_plex_found_on_users_list ) ) if show: shows_in_plex_matching_users_list += show show_media_entry_mapped_to_plex.append(entry) EventLogHelper.log_info( f"Fetched list of shows by title, returned {len(shows_in_plex_matching_users_list)} results.\n" f"Items which could not be found in plex {len(shows_missing_in_plex_found_on_users_list)}.", self.__class__.__name__, inspect.currentframe().f_code.co_name ) return DownloadableQueue( shows_in_plex_matching_users_list, shows_missing_in_plex_found_on_users_list, show_media_entry_mapped_to_plex )
def __search_for_shows(anime_section: ShowSection, media_entry: MediaEntry) -> SearchResult: search_results: List[Show] = list() search_match_term_match: str = "" search_terms: List[str] = media_entry.generate_search_terms() for search_term in search_terms: search_results = anime_section.search(title=search_term) if search_results: search_match_term_match = search_term break filtered_search_results: List[Show] = list( filter( lambda show: PlexController.__matches_search_term( show.title, search_match_term_match ), search_results) ) if filtered_search_results: EventLogHelper.log_info( f"Search term match found `{search_match_term_match}` -> `{filtered_search_results}`", "PlexController", inspect.currentframe().f_code.co_name ) return SearchResult( filtered_search_results, search_match_term_match )
def save_or_update(self, value: Optional[Dict]): result_ids: List[Any] = self.db.upsert(value, where('id') == value['id']) if result_ids.__len__() < 1: EventLogHelper.log_error(f"Error objects to {ANILIST_DATABASE}", self.__class__.__name__, inspect.currentframe().f_code.co_name, logging.CRITICAL)
def _find_missing_episodes( self, show: Optional[Show], search_results: Optional[List[TorrentInfo]] ) -> List[Optional[TorrentInfo]]: """ Find episodes that might not exist and add then to the download queue :return: """ torrent_matches: List[Optional[TorrentInfo]] = list() for search_result in search_results: sleep(self.sleep_duration) if not search_result.added_anime_info(): continue anime_info = search_result.anime_info if f"[{anime_info.release_group}]" != self.config.torrent_preferred_group: continue is_episode_present = False for episode in show.episodes(): is_episode_present = episode.index == int( float(anime_info.episode_number)) if is_episode_present: break if not is_episode_present: EventLogHelper.log_info( f"Adding missing episode: `{anime_info.file_name}`", self.__class__.__name__, inspect.currentframe().f_code.co_name) torrent_matches.append(search_result) return torrent_matches
def added_anime_info(self) -> bool: """ Added anime info for the the current torrent :return: true if successful otherwise false if the release is a Batch """ parsed_file_name = "" try: parsed_file_name = anitopy.parse(self.name) if not isinstance(parsed_file_name['episode_number'], str) \ or parsed_file_name.__contains__('release_information') \ and parsed_file_name['release_information'] == 'Batch': print() EventLogHelper.log_info( f"Skipping torrent : `{self.name}` | {parsed_file_name}", self.__class__.__name__, inspect.currentframe().f_code.co_name) print( '<------------------------------------------------------------>' ) return False else: torrent_name_info = from_dict(TorrentAnimeInfo, parsed_file_name) self.anime_info = torrent_name_info return True except Exception as e: print() EventLogHelper.log_info( f"Error converting dictionary to data class\n" f"Details: {e} | {parsed_file_name}", self.__class__.__name__, inspect.currentframe().f_code.co_name) print( '<------------------------------------------------------------>' ) return False
def __handle_response(self, media_collection_list: Optional[List[Dict]], anilist_store: AniListStore): try: for item in media_collection_list: for entry in item['entries']: anilist_store.save_or_update(entry) except Exception as e: EventLogHelper.log_error(f"Error handling response -> {e}", self.__class__.__name__, inspect.currentframe().f_code.co_name, logging.CRITICAL)
def create_dictionary_class(self, response: MediaEntry) -> Optional[Dict]: parsed_dictionary: Optional[Dict] = None try: parsed_dictionary = dict(response) except Exception as e: print() EventLogHelper.log_info(f"Error converting data class to dictionary\n" f"details -> {e}", self.__class__.__name__, inspect.currentframe().f_code.co_name) print('<------------------------------------------------------------>') return parsed_dictionary
def create_data_class(self, response: Optional[Dict]) -> Optional[MediaEntry]: parsed_object: Optional[List[MediaEntry]] = None try: parsed_object = from_dict(MediaEntry, response) except Exception as e: print() EventLogHelper.log_info(f"Error converting dictionary to data class\n" f"details -> {e}", self.__class__.__name__, inspect.currentframe().f_code.co_name) print('<------------------------------------------------------------>') return parsed_object
def get_all(self) -> List[Optional[TorrentInfo]]: query_results: List[Optional[TorrentInfo]] = list() documents: List[Document] = self.db.all() try: for document in documents: data_class = self.model_helper.create_data_class(document) query_results.append(data_class) except Exception as e: EventLogHelper.log_error( f"Database value is not in a valid format {APP_DATABASE}\n" f"Details: {e}", self.__class__.__name__, inspect.currentframe().f_code.co_name, logging.CRITICAL) return query_results
def __init__(self) -> None: super().__init__() try: self.config = json.loads(StorageUtil.read_file('config', 'plex.json')) auth = json.loads(StorageUtil.read_file("auth", "credentials.json")) self.plex = PlexServer(auth["url"], auth["token"]) except Exception as e: EventLogHelper.log_error( f"Encountered exception while initializing controller -> {e}", self.__class__.__name__, inspect.currentframe().f_code.co_name, logging.CRITICAL )
def __search_for_matching_until_found(search_page: int, search_terms: List[str]): for search_term in search_terms: # noinspection PyTypeChecker,PyCallByClass search_results = Nyaa.search(keyword=search_term, category='1', page=search_page) if search_results: EventLogHelper.log_info( f"Nyaa search results found for search term: `{search_term}` | on page: `{search_page}`" f" | found `{len(search_results)}` results", "NyaaController", inspect.currentframe().f_code.co_name) return search_results
def save_or_update(self, value: Optional[Dict]): result_ids: List[Any] = list() try: result_ids += self.db.upsert(value, where('name') == value['name']) except Exception as e: EventLogHelper.log_error( f"Error saving or updating model to {value}\n" f"Details: {e}", self.__class__.__name__, inspect.currentframe().f_code.co_name, logging.CRITICAL) if len(result_ids) < 1: EventLogHelper.log_error(f"Error objects to {APP_DATABASE}", self.__class__.__name__, inspect.currentframe().f_code.co_name, logging.CRITICAL)
def create_data_class( self, response: Dict[Optional[str], Optional[str]]) -> Optional[TorrentInfo]: parsed_object: Optional[TorrentInfo] = None try: parsed_object = from_dict(TorrentInfo, response) except Exception as e: print() EventLogHelper.log_info( f"Error converting dictionary to data class\n" f"Details: {e}", self.__class__.__name__, inspect.currentframe().f_code.co_name) print( '<------------------------------------------------------------>' ) return parsed_object
def search_nyaa_for_shows(self, download_queue: DownloadableQueue) -> Optional[List[TorrentInfo]]: """ Searches nyaa.si for torrents matching the tittle name/s :param download_queue: a model consisting of a tuple shows and media entries of missing episodes :return: a list of torrent results """ torrent_search_result_list: List[TorrentInfo] = list() torrent_search_result_list_for_missing_shows: List[TorrentInfo] = list() print() print('-------------------------------------------------------') EventLogHelper.log_info( f"Searching for missing items in plex", self.__class__.__name__, inspect.currentframe().f_code.co_name ) for media in download_queue.shows_missing_in_plex: torrent_search_results = self.nyaa_controller.search_for_missing_shows(media, self.app_config) if len(torrent_search_results) > 0: torrent_search_result_list_for_missing_shows += torrent_search_results else: EventLogHelper.log_info( f"Unable to find {media.media.title.userPreferred} from nyaa.si", self.__class__.__name__, inspect.currentframe().f_code.co_name ) print('-------------------------------------------------------') print() print() print('-------------------------------------------------------') EventLogHelper.log_info( f"Searching for matching items in plex", self.__class__.__name__, inspect.currentframe().f_code.co_name ) for show, media in zip(download_queue.shows_found_in_plex, download_queue.show_media_entry_in_plex): torrent_search_results = self.nyaa_controller.search_for_shows( show, media, self.app_config ) if len(torrent_search_results) > 0: print() torrent_search_result_list += torrent_search_results else: EventLogHelper.log_info( f"No new releases found for the following torrent/s `{media.generate_search_terms()}` on nyaa.si", self.__class__.__name__, inspect.currentframe().f_code.co_name ) print() print('-------------------------------------------------------') print() return torrent_search_result_list + torrent_search_result_list_for_missing_shows
def __init__(self) -> None: super().__init__() try: __config = json.loads( StorageUtil.read_file('auth', 'credentials.json')) if __config is not None: credentials = __config["transmission"] self.client = Client(host=credentials["host"], port=credentials["port"], username=credentials["username"], password=credentials["password"]) else: self.client = Client() except Exception as e: EventLogHelper.log_error( f"Encountered exception while initializing controller -> {e}", self.__class__.__name__, inspect.currentframe().f_code.co_name, logging.CRITICAL)
def __move_torrent_to_monitored_directory(self, torrent_info: TorrentInfo): try: StorageUtil.copy_or_move_file( filename=f"{torrent_info.name}.torrent", directory_path=self.app_config.build_parent_save_path( torrent_info.anime_info.anime_title ), destination_path=self.app_config.torrent_monitor_directory, keep_file=self.app_config.torrent_keep_file_after_queuing ) model = self.nyaa_model_helper.create_dictionary_class(torrent_info) self.app_store.save_or_update(model) except Exception as e: EventLogHelper.log_error( f"__move_torrent_to_monitored_directory -> StorageUtil.copy_or_move_file -> {e}", self.__class__.__name__, inspect.currentframe().f_code.co_name )
def add_torrent_magnet(self, filename: str) -> bool: """ adds a magnet link instead of the actual torrent file contents :param filename: like of where the file can be found or path to actual file :return: True if the operation was a success otherwise False """ try: torrent = self.client.torrent.add(filename=filename) sleep(.5) EventLogHelper.log_info( f"Added torrent file url to torrent client -> {torrent} | {filename}", self.__class__.__name__, inspect.currentframe().f_code.co_name) return True except Exception as e: EventLogHelper.log_warning( f"Unable to add torrent to transmission -> {e}", self.__class__.__name__, inspect.currentframe().f_code.co_name) return False
def download_torrent_file(torrent_info: TorrentInfo, config: AppConfig) -> bool: """ Downloads a .torrent file and saves it into the app/torrents/ directory :param config: configuration class :param torrent_info: :return: True if the operation was successful otherwise False """ try: print() torrent_file_name = f"{torrent_info.anime_info.file_name}.torrent" response: Response = get(url=torrent_info.download_url, allow_redirects=True, stream=True, timeout=30.0) if response.ok: StorageUtil.write_file_in_app( directory_path=config.build_parent_save_path( torrent_info.anime_info.anime_title), filename=torrent_file_name, contents=response, write_mode='wb') else: EventLogHelper.log_info( f"Requesting torrent for download failed : {response}\n" f"Retrying in 5 seconds..", "NyaaController", inspect.currentframe().f_code.co_name) sleep(5) NyaaController.download_torrent_file(torrent_info, config) print( '<------------------------------------------------------------>' ) except Exception as e: EventLogHelper.log_error( f"Encountered exception while downloading torrent file -> {e}", "NyaaController", inspect.currentframe().f_code.co_name, logging.CRITICAL) return False return True
def add_torrent(self, file_path: str, file_name: str): """ adds the torrent from an existing file path :param file_name: name of the file :param file_path: where the file can be found :return: True if the operation was a success otherwise False """ try: file_contents = StorageUtil.read_file(file_path, file_name) torrent = self.client.torrent.add(metainfo=file_contents) sleep(1.5) EventLogHelper.log_info( f"Added torrent file to download client -> {torrent} | {file_path}", self.__class__.__name__, inspect.currentframe().f_code.co_name) return True except Exception as e: EventLogHelper.log_warning( f"Unable to add torrent to transmission -> {e}", self.__class__.__name__, inspect.currentframe().f_code.co_name) return False
def start_application(self) -> None: """ Application starting point :return: """ try: anime_list: List[Optional[MediaEntry]] = self.fetch_anime_list() print('-------------------------------------------------------') if anime_list: download_queue: DownloadableQueue = self.find_plex_show(anime_list) print('-------------------------------------------------------') if download_queue.contains_items(): print('-------------------------------------------------------') search_results = self.search_nyaa_for_shows(download_queue) if search_results is not None and len(search_results) > 0: for torrent_info in search_results: if torrent_info.anime_info is None: print() EventLogHelper.log_info( f"Skipping torrent without anime info -> {torrent_info}", self.__class__.__name__, inspect.currentframe().f_code.co_name ) continue downloaded_torrent = self.__find_downloadable_torrents(torrent_info) if downloaded_torrent is None or len(downloaded_torrent) < 1: queued: bool = self.__queue_downloaded_torrent_file(torrent_info) if not queued: self.__download_torrent_file(torrent_info) else: print() EventLogHelper.log_info( f"Skipping existing download -> {torrent_info.anime_info}", self.__class__.__name__, inspect.currentframe().f_code.co_name ) else: print() EventLogHelper.log_info( f"No new episodes to download, ending execution of script", self.__class__.__name__, inspect.currentframe().f_code.co_name ) print('-------------------------------------------------------') except Exception as e: EventLogHelper.log_error(f"Uncaught exception thrown -> {e}", self.__class__.__name__, inspect.currentframe().f_code.co_name)
def find_all_by_title(self, media_entry: MediaEntry, add_missing) -> List[Optional[Show]]: """ Search for plex shows within a configuration given library name :param add_missing: :param media_entry: media to look for :return: list of optional shows """ all_shows = list() try: anime_section: Optional[ShowSection] = self.plex.library.section(self.config['section_library_name']) if anime_section is not None: search_result: SearchResult = self.__search_for_shows(anime_section, media_entry) if search_result.search_results: for show in search_result.search_results: show_episodes_count = len(show.episodes()) episodes = media_entry.media.episodes if show_episodes_count >= episodes: continue all_shows.append(show) else: print() search_term = media_entry.media.title.userPreferred EventLogHelper.log_warning( f"Search term not found `{search_term}`, adding to missing shows list", self.__class__.__name__, inspect.currentframe().f_code.co_name ) print() add_missing() except Exception as e: EventLogHelper.log_info( f"{e}", self.__class__.__name__, inspect.currentframe().f_code.co_name ) return all_shows
def __download_torrent_file(self, torrent_info: TorrentInfo): print() EventLogHelper.log_info(f"Downloading torrent for file -> {torrent_info.name}", self.__class__.__name__, inspect.currentframe().f_code.co_name) is_download_successful = self.nyaa_controller.download_torrent_file(torrent_info, self.app_config) if is_download_successful: model_dictionary = self.nyaa_model_helper.create_dictionary_class(torrent_info) self.app_store.save_or_update(model_dictionary) print() EventLogHelper.log_info(f"Download successful, anime attributes -> {torrent_info.anime_info}", self.__class__.__name__, inspect.currentframe().f_code.co_name) self.__move_torrent_to_monitored_directory(torrent_info) else: print() EventLogHelper.log_info(f"Failed to download, anime attributes -> {torrent_info.anime_info}", self.__class__.__name__, inspect.currentframe().f_code.co_name)