def completed_media_task(): nefarious_settings = NefariousSettings.get() transmission_client = get_transmission_client(nefarious_settings) incomplete_kwargs = dict(collected=False, transmission_torrent_hash__isnull=False) movies = WatchMovie.objects.filter(**incomplete_kwargs) tv_seasons = WatchTVSeason.objects.filter(**incomplete_kwargs) tv_episodes = WatchTVEpisode.objects.filter(**incomplete_kwargs) incomplete_media = list(movies) + list(tv_episodes) + list(tv_seasons) for media in incomplete_media: try: torrent = transmission_client.get_torrent( media.transmission_torrent_hash) if torrent.progress == 100: logging.info('Media completed: {}'.format(media)) media.collected = True media.collected_date = datetime.utcnow() media.save() except KeyError: logging.info( "Media's torrent no longer present, removing reference: {}". format(media)) media.transmission_torrent_hash = None media.save()
def blacklist_auto_retry(self, request, pk): watch_media = self.get_object() nefarious_settings = NefariousSettings.get() # add to blacklist logger_foreground.info('Blacklisting {}'.format( watch_media.transmission_torrent_hash)) TorrentBlacklist.objects.get_or_create( hash=watch_media.transmission_torrent_hash, defaults=dict(name=str(watch_media), )) # unset previous details del_transmission_torrent_hash = watch_media.transmission_torrent_hash watch_media.transmission_torrent_hash = None watch_media.collected = False watch_media.collected_date = None watch_media.save() # re-queue search self._watch_media_task(watch_media_id=watch_media.id) # remove torrent and delete data logger_foreground.info('Removing blacklisted torrent hash: {}'.format( del_transmission_torrent_hash)) transmission_client = get_transmission_client( nefarious_settings=nefarious_settings) transmission_client.remove_torrent([del_transmission_torrent_hash], delete_data=True) return Response(self.serializer_class(watch_media).data)
def __init__(self, watch_media_id: int): self.nefarious_settings = NefariousSettings.get() self.tmdb_client = get_tmdb_client(self.nefarious_settings) self.transmission_client = get_transmission_client( self.nefarious_settings) self.watch_media = self._get_watch_media(watch_media_id) self.tmdb_media = self._get_tmdb_media()
def completed_media_task(): nefarious_settings = NefariousSettings.get() transmission_client = get_transmission_client(nefarious_settings) incomplete_kwargs = dict(collected=False, transmission_torrent_hash__isnull=False) movies = WatchMovie.objects.filter(**incomplete_kwargs) tv_seasons = WatchTVSeason.objects.filter(**incomplete_kwargs) tv_episodes = WatchTVEpisode.objects.filter(**incomplete_kwargs) incomplete_media = list(movies) + list(tv_episodes) + list(tv_seasons) for media in incomplete_media: try: torrent = transmission_client.get_torrent(media.transmission_torrent_hash) except KeyError: # media's torrent reference no longer exists so remove the reference logging.info("Media's torrent no longer present, removing reference: {}".format(media)) media.transmission_torrent_hash = None media.save() else: # download is complete if torrent.progress == 100: # flag media as completed logging.info('Media completed: {}'.format(media)) media.collected = True media.collected_date = datetime.utcnow() media.save() # special handling for tv seasons if isinstance(media, WatchTVSeason): # mark season request complete for season_request in WatchTVSeasonRequest.objects.filter(watch_tv_show=media.watch_tv_show, season_number=media.season_number): season_request.collected = True season_request.save() # delete any individual episodes now that we have the whole season for episode in WatchTVEpisode.objects.filter(watch_tv_show=media.watch_tv_show, season_number=media.season_number): episode.delete() # rename the torrent file/path renamed_torrent_name = get_renamed_torrent(torrent, media) logging.info('Renaming torrent file from "{}" to "{}"'.format(torrent.name, renamed_torrent_name)) transmission_client.rename_torrent_path(torrent.id, torrent.name, renamed_torrent_name) # move data from staging path to actual complete path dir_name = ( nefarious_settings.transmission_movie_download_dir if isinstance(media, WatchMovie) else nefarious_settings.transmission_tv_download_dir ) transmission_session = transmission_client.session_stats() move_to_path = os.path.join( transmission_session.download_dir, dir_name.lstrip('/'), ) logging.info('Moving torrent data to "{}"'.format(move_to_path)) torrent.move_data(move_to_path)
def perform_destroy(self, instance: WatchMediaBase): # delete transmission result, including data, if it still exists nefarious_settings = NefariousSettings.get() transmission_client = get_transmission_client(nefarious_settings) transmission_client.remove_torrent( [instance.transmission_torrent_hash], delete_data=True) # fails silently super().perform_destroy(instance)
def destroy_transmission_result(instance: WatchMediaBase): # delete transmission result, including data, if it still exists nefarious_settings = NefariousSettings.get() try: transmission_client = get_transmission_client(nefarious_settings) transmission_client.remove_torrent( [instance.transmission_torrent_hash], delete_data=True, timeout=10) except Exception as e: logging.warning(str(e)) logging.warning('could not destroy torrent in transmission')
def get(self, request): nefarious_settings = NefariousSettings.get() transmission_client = get_transmission_client(nefarious_settings) watch_movies = request.query_params.getlist('watch_movies', []) watch_tv_shows = request.query_params.getlist('watch_tv_shows', []) results = [] querysets = [] # movies if watch_movies: querysets.append(WatchMovie.objects.filter(id__in=watch_movies)) # tv shows if watch_tv_shows: querysets.append( WatchTVEpisode.objects.filter( watch_tv_show__id__in=watch_tv_shows)) querysets.append( WatchTVSeason.objects.filter( watch_tv_show__id__in=watch_tv_shows)) for qs in querysets: for media in qs: if isinstance(media, WatchTVSeason): media_serializer = WatchTVSeasonSerializer elif isinstance(media, WatchTVEpisode): media_serializer = WatchTVEpisodeSerializer else: media_serializer = WatchMovieSerializer result = { 'watchMedia': media_serializer(media).data, } if media.transmission_torrent_hash: try: torrent = transmission_client.get_torrent( media.transmission_torrent_hash) except (KeyError, ValueError ): # torrent no longer exists or was invalid pass except Exception as e: logger_foreground.error(str(e)) raise e else: result['torrent'] = TransmissionTorrentSerializer( torrent).data results.append(result) return Response(results)
def perform_destroy(self, instance: WatchMediaBase): # delete transmission result, including data, if it still exists nefarious_settings = NefariousSettings.get() try: transmission_client = get_transmission_client(nefarious_settings) transmission_client.remove_torrent( [instance.transmission_torrent_hash], delete_data=True) except: logging.warning( 'could not connect to transmission to delete the torrent') super().perform_destroy(instance)
def get(self, request): nefarious_settings = NefariousSettings.get() transmission_client = get_transmission_client(nefarious_settings) watch_movies = request.query_params.getlist('watch_movies') watch_tv_episodes = request.query_params.getlist('watch_tv_episodes') watch_tv_seasons = request.query_params.getlist('watch_tv_seasons') querysets = [] torrents = [] torrent_hashes = [] # movies if watch_movies: querysets.append(WatchMovie.objects.filter(id__in=watch_movies)) # tv episodes if watch_tv_episodes: querysets.append( WatchTVEpisode.objects.filter(id__in=watch_tv_episodes)) # tv seasons if watch_tv_seasons: querysets.append( WatchTVSeason.objects.filter(id__in=watch_tv_seasons)) for query in querysets: torrent_hashes += [ media.transmission_torrent_hash for media in query if media.transmission_torrent_hash ] for torrent_hash in torrent_hashes: try: torrent = transmission_client.get_torrent(torrent_hash) torrents.append(torrent) except (KeyError, ValueError): # torrent no longer exists or was invalid continue except Exception as e: logging.error(str(e)) raise e return Response( TransmissionTorrentSerializer(torrents, many=True).data)
def post(self, request): nefarious_settings = NefariousSettings.get() torrent = request.data.get('torrent') media_type = request.data.get('media_type', MEDIA_TYPE_TV) if not is_magnet_url(torrent): torrent = swap_jackett_host(torrent, nefarious_settings) if not torrent: return Response({ 'success': False, 'error': 'Missing torrent link' }) try: torrent = trace_torrent_url(torrent) except Exception as e: return Response({ 'success': False, 'error': 'An unknown error occurred', 'error_detail': str(e) }) logging.info('adding torrent: {}'.format(torrent)) # add torrent transmission_client = get_transmission_client(nefarious_settings) transmission_session = transmission_client.session_stats() if media_type == MEDIA_TYPE_MOVIE: download_dir = os.path.join( transmission_session.download_dir, nefarious_settings.transmission_movie_download_dir.lstrip('/')) else: download_dir = os.path.join( transmission_session.download_dir, nefarious_settings.transmission_tv_download_dir.lstrip('/')) transmission_client.add_torrent( torrent, paused=True, download_dir=download_dir, ) return Response({'success': True})
def completed_media_task(): nefarious_settings = NefariousSettings.get() transmission_client = get_transmission_client(nefarious_settings) incomplete_kwargs = dict(collected=False, transmission_torrent_hash__isnull=False) movies = WatchMovie.objects.filter(**incomplete_kwargs) tv_seasons = WatchTVSeason.objects.filter(**incomplete_kwargs) tv_episodes = WatchTVEpisode.objects.filter(**incomplete_kwargs) incomplete_media = list(movies) + list(tv_episodes) + list(tv_seasons) for media in incomplete_media: try: torrent = transmission_client.get_torrent( media.transmission_torrent_hash) except KeyError: logging.info( "Media's torrent no longer present, removing reference: {}". format(media)) media.transmission_torrent_hash = None media.save() else: if torrent.progress == 100: logging.info('Media completed: {}'.format(media)) media.collected = True media.collected_date = datetime.utcnow() media.save() # if season is complete, mark season request complete if isinstance(media, WatchTVSeason): for season_request in WatchTVSeasonRequest.objects.filter( watch_tv_show=media.watch_tv_show, season_number=media.season_number): season_request.collected = True season_request.save()
def post(self, request): result = { 'success': True, } nefarious_settings = NefariousSettings.get() tmdb_media = request.data.get('tmdb_media', {}) torrent_info = request.data.get('torrent', {}) torrent_url = torrent_info.get('MagnetUri') or torrent_info.get('Link') if not torrent_url: return Response({ 'success': False, 'error': 'Missing torrent link' }) media_type = request.data.get('media_type', MEDIA_TYPE_TV) # validate tv if media_type == MEDIA_TYPE_TV: if 'season_number' not in request.data: return Response({ 'success': False, 'error': 'Missing season_number' }) if not is_magnet_url(torrent_url): torrent_url = swap_jackett_host(torrent_url, nefarious_settings) try: torrent_url = trace_torrent_url(torrent_url) except Exception as e: return Response({ 'success': False, 'error': 'An unknown error occurred', 'error_detail': str(e) }) logger_foreground.info('adding torrent: {}'.format(torrent_url)) # add torrent transmission_client = get_transmission_client(nefarious_settings) transmission_session = transmission_client.session_stats() tmdb = get_tmdb_client(nefarious_settings) # set download paths and associate torrent with watch instance if media_type == MEDIA_TYPE_MOVIE: tmdb_request = tmdb.Movies(tmdb_media['id']) tmdb_movie = tmdb_request.info() watch_media = WatchMovie( user=request.user, tmdb_movie_id=tmdb_movie['id'], name=tmdb_movie['title'], poster_image_url=nefarious_settings.get_tmdb_poster_url( tmdb_movie['poster_path']), release_date=parse_date(tmdb_movie['release_date'] or ''), ) watch_media.save() download_dir = os.path.join( transmission_session.download_dir, nefarious_settings.transmission_movie_download_dir.lstrip('/')) result['watch_movie'] = WatchMovieSerializer(watch_media).data else: tmdb_request = tmdb.TV(tmdb_media['id']) tmdb_show = tmdb_request.info() watch_tv_show, _ = WatchTVShow.objects.get_or_create( tmdb_show_id=tmdb_show['id'], defaults=dict( user=request.user, name=tmdb_show['name'], poster_image_url=nefarious_settings.get_tmdb_poster_url( tmdb_show['poster_path']), )) result['watch_tv_show'] = WatchTVShowSerializer(watch_tv_show).data # single episode if 'episode_number' in request.data: tmdb_request = tmdb.TV_Episodes(tmdb_media['id'], request.data['season_number'], request.data['episode_number']) tmdb_episode = tmdb_request.info() watch_media = WatchTVEpisode( user=request.user, watch_tv_show=watch_tv_show, tmdb_episode_id=tmdb_episode['id'], season_number=request.data['season_number'], episode_number=request.data['episode_number'], release_date=parse_date(tmdb_episode['air_date'] or '')) watch_media.save() result['watch_tv_episode'] = WatchTVEpisodeSerializer( watch_media).data # entire season else: season_result = tmdb.TV_Seasons(tmdb_show['id'], request.data['season_number']) season_data = season_result.info() # create the season request watch_tv_season_request, _ = WatchTVSeasonRequest.objects.get_or_create( watch_tv_show=watch_tv_show, season_number=request.data['season_number'], defaults=dict( user=request.user, collected= True, # set collected since we're directly downloading a torrent release_date=parse_date(season_data['air_date'] or '')), ) # create the actual watch season instance watch_media = WatchTVSeason( user=request.user, watch_tv_show=watch_tv_show, season_number=request.data['season_number'], release_date=parse_date(season_data['air_date'] or '')) watch_media.save() # return the season request vs the watch instance result[ 'watch_tv_season_request'] = WatchTVSeasonRequestSerializer( watch_tv_season_request).data download_dir = os.path.join( transmission_session.download_dir, nefarious_settings.transmission_tv_download_dir.lstrip('/')) torrent = transmission_client.add_torrent( torrent_url, paused=settings.DEBUG, download_dir=download_dir, ) watch_media.transmission_torrent_hash = torrent.hashString watch_media.save() return Response(result)
def verify_settings_transmission(nefarious_settings: NefariousSettings): # verify transmission try: get_transmission_client(nefarious_settings) except TransmissionError: raise Exception('Could not connect to transmission')
def completed_media_task(): nefarious_settings = NefariousSettings.get() transmission_client = get_transmission_client(nefarious_settings) incomplete_kwargs = dict(collected=False, transmission_torrent_hash__isnull=False) movies = WatchMovie.objects.filter(**incomplete_kwargs) tv_seasons = WatchTVSeason.objects.filter(**incomplete_kwargs) tv_episodes = WatchTVEpisode.objects.filter(**incomplete_kwargs) incomplete_media = list(movies) + list(tv_episodes) + list(tv_seasons) for media in incomplete_media: try: torrent = transmission_client.get_torrent( media.transmission_torrent_hash) except KeyError: # media's torrent reference no longer exists so remove the reference logging.info( "Media's torrent no longer present, removing reference: {}". format(media)) media.transmission_torrent_hash = None media.save() else: # download is complete if torrent.progress == 100: # flag media as completed logging.info('Media completed: {}'.format(media)) media.collected = True media.collected_date = datetime.utcnow() media.save() # special handling for tv seasons if isinstance(media, WatchTVSeason): # mark season request complete for season_request in WatchTVSeasonRequest.objects.filter( watch_tv_show=media.watch_tv_show, season_number=media.season_number): season_request.collected = True season_request.save() # get the sub path (ie. "movies/", "tv/') so we can move the data from staging sub_path = (nefarious_settings.transmission_movie_download_dir if isinstance(media, WatchMovie) else nefarious_settings.transmission_tv_download_dir ).lstrip('/') # get the path and updated name for the data new_path, new_name = get_media_new_path_and_name( media, torrent.name, len(torrent.files()) == 1) # move the data transmission_session = transmission_client.session_stats() move_to_path = os.path.join( transmission_session.download_dir, sub_path, new_path or '', ) logging.info( 'Moving torrent data to "{}"'.format(move_to_path)) torrent.move_data(move_to_path) # rename the data logging.info('Renaming torrent file from "{}" to "{}"'.format( torrent.name, new_name)) transmission_client.rename_torrent_path( torrent.id, torrent.name, new_name) # send websocket message media was updated media_type, data = websocket.get_media_type_and_serialized_watch_media( media) websocket.send_message(websocket.ACTION_UPDATED, media_type, data) # send complete message through webhook webhook.send_message('{} was downloaded'.format(media))
def fetch(self): logger_background.info('Processing request to watch {}'.format( self.watch_media)) valid_search_results = [] search = self._get_search_results() # save this attempt date self.watch_media.last_attempt_date = datetime.utcnow() self.watch_media.save() if search.ok: for result in search.results: if self.is_match(result['Title']): valid_search_results.append(result) else: logger_background.info('Not matched: {}'.format( result['Title'])) if valid_search_results: # trace the "torrent url" (sometimes magnet) in each valid result valid_search_results = self._results_with_valid_urls( valid_search_results) while valid_search_results: logger_background.info('Valid Search Results: {}'.format( len(valid_search_results))) # find the torrent result with the highest weight (i.e seeds) best_result = self._get_best_torrent_result( valid_search_results) transmission_client = get_transmission_client( self.nefarious_settings) transmission_session = transmission_client.session_stats() # add to transmission torrent = transmission_client.add_torrent( best_result['torrent_url'], paused= True, # start paused to we can verify if the torrent has been blacklisted download_dir=self._get_download_dir( transmission_session), ) # verify it's not blacklisted and save & start this torrent if not TorrentBlacklist.objects.filter( hash=torrent.hashString).exists(): logger_background.info('Adding torrent for {}'.format( self.tmdb_media[self._get_tmdb_title_key()])) logger_background.info( 'Added torrent {} with {} seeders'.format( best_result['Title'], best_result['Seeders'])) logger_background.info( 'Starting torrent id: {} and hash {}'.format( torrent.id, torrent.hashString)) # save torrent details on our watch instance self._save_torrent_details(torrent) # start the torrent if not settings.DEBUG: torrent.start() return True else: # remove the blacklisted/paused torrent and continue to the next result logger_background.info( 'BLACKLISTED: {} ({}) - trying next best result'. format(best_result['Title'], torrent.hashString)) transmission_client.remove_torrent([torrent.id]) valid_search_results.remove(best_result) continue else: logger_background.info('No valid search results for {}'.format( self.tmdb_media[self._get_tmdb_title_key()])) else: logger_background.info('Search error: {}'.format( search.error_content)) logger_background.info('Unable to find any results for {}'.format( self.tmdb_media[self._get_tmdb_title_key()])) return False
def completed_media_task(): nefarious_settings = NefariousSettings.get() transmission_client = get_transmission_client(nefarious_settings) incomplete_kwargs = dict(collected=False, transmission_torrent_hash__isnull=False) movies = WatchMovie.objects.filter(**incomplete_kwargs) tv_seasons = WatchTVSeason.objects.filter(**incomplete_kwargs) tv_episodes = WatchTVEpisode.objects.filter(**incomplete_kwargs) incomplete_media = list(movies) + list(tv_episodes) + list(tv_seasons) for media in incomplete_media: try: torrent = transmission_client.get_torrent( media.transmission_torrent_hash) except KeyError: # media's torrent reference no longer exists so remove the reference logger_background.info( "Media's torrent no longer present, removing reference: {}". format(media)) media.transmission_torrent_hash = None media.save() else: # download is complete if torrent.progress == 100: # flag media as completed logger_background.info('Media completed: {}'.format(media)) # special handling for tv seasons if isinstance(media, WatchTVSeason): # mark season request complete for season_request in WatchTVSeasonRequest.objects.filter( watch_tv_show=media.watch_tv_show, season_number=media.season_number): season_request.collected = True season_request.save() # get the sub path (ie. "movies/", "tv/') so we can move the data from staging sub_path = (nefarious_settings.transmission_movie_download_dir if isinstance(media, WatchMovie) else nefarious_settings.transmission_tv_download_dir ).lstrip('/') # get the path and updated name for the data new_path, new_name = get_media_new_path_and_name( media, torrent.name, len(torrent.files()) == 1) relative_path = os.path.join( sub_path, # i.e "movies" or "tv" new_path or '', ) # move the data to a new location transmission_session = transmission_client.session_stats() transmission_move_to_path = os.path.join( transmission_session.download_dir, relative_path, ) logger_background.info('Moving torrent data to "{}"'.format( transmission_move_to_path)) torrent.move_data(transmission_move_to_path) # rename the data logger_background.info( 'Renaming torrent file from "{}" to "{}"'.format( torrent.name, new_name)) transmission_client.rename_torrent_path( torrent.id, torrent.name, new_name) # save media as collected media.collected = True media.collected_date = timezone.now() media.save() # send websocket message media was updated media_type, data = websocket.get_media_type_and_serialized_watch_media( media) websocket.send_message(websocket.ACTION_UPDATED, media_type, data) # send complete message through webhook webhook.send_message('{} was downloaded'.format(media)) # define the import path import_path = os.path.join( settings.INTERNAL_DOWNLOAD_PATH, relative_path, # new_path will be None if the torrent is already a directory so fall back to the new name new_path or new_name, ) # post-tasks post_tasks = [ # queue import of media to save the actual media paths import_library_task.si( 'movie' if isinstance(media, WatchMovie) else 'tv', # media type media.user_id, # user id import_path, ), ] # conditionally add subtitles task to post-tasks if nefarious_settings.should_save_subtitles() and isinstance( media, (WatchMovie, WatchTVEpisode)): post_tasks.append( download_subtitles_task.si(media_type.lower(), media.id)) # queue post-tasks chain(*post_tasks)()
def cross_seed_task(torrent_hash: str): # TODO - this isn't used and is a work in progress nefarious_settings = NefariousSettings.get() transmission_client = get_transmission_client(nefarious_settings) try: torrent = transmission_client.get_torrent(torrent_hash) except KeyError: logging.warning('{} no longer in transmission'.format(torrent_hash)) return logging.info('Attempting cross seed for {}'.format(torrent.name)) valid_results = [] search = SearchTorrents(MEDIA_TYPE_MOVIE, torrent.name, search_seed_only=True) if search.ok: for result in search.results: transmission_client.add_torrent() normalized_title = regex.sub(ParserBase.word_delimiter_regex, ' ', torrent.name) if result['Title'].lower() in [ torrent.name.lower(), normalized_title.lower() ]: # TODO - the sizes won't match due to the inaccuracy of the scraping values # add paused torrent and verify the sizes match valid_results.append(result) # trace the "torrent url" (sometimes magnet) in each valid result valid_results = results_with_valid_urls(valid_results, nefarious_settings) logging.info(valid_results) seed_only_indexers = get_seed_only_indexers(nefarious_settings) if valid_results: for seed_only_indexer in seed_only_indexers: logging.info( 'Looking for cross seed results for indexer {}'.format( seed_only_indexer)) # filter results to this seed-only indexer indexer_results = [ r for r in valid_results if r['TrackerId'] == seed_only_indexer ] if not indexer_results: logging.info( 'Indexer {} does not have any results for {}'.format( seed_only_indexer, torrent.name)) continue # get the "best" result best_result = get_best_torrent_result(indexer_results) logging.info('Cross seeding {} for indexer {}'.format( best_result['Title'], best_result['Tracker'])) # add a new key to our result object with the traced torrent url best_result[ 'torrent_url'] = best_result['MagnetUri'] or trace_torrent_url( swap_jackett_host(best_result['Link'], nefarious_settings)) # add to transmission transmission_session = transmission_client.session_stats() torrent = transmission_client.add_torrent( best_result['torrent_url'], paused=True, # TODO # TODO #download_dir=self._get_download_dir(transmission_session) ) else: logging.info('No valid cross seeding options for {}'.format( torrent.name))