From df7aa364aa17b78f71e61d995317712bc4a8ac04 Mon Sep 17 00:00:00 2001 From: echel0n Date: Fri, 27 Jun 2014 04:00:16 -0700 Subject: [PATCH] Added feature that gets all recommended shows from your trakt.tv account and lets you add the show like you would if adding a new show and searching. --- .../interfaces/default/home_addShows.tmpl | 12 +- .../default/home_recommendedShows.tmpl | 64 ++++++++ gui/slick/js/addExistingShow.js | 2 +- gui/slick/js/recommendedShows.js | 144 ++++++++++++++++++ sickbeard/notifiers/trakt.py | 22 ++- sickbeard/webserve.py | 66 +++++++- 6 files changed, 302 insertions(+), 8 deletions(-) create mode 100644 gui/slick/interfaces/default/home_recommendedShows.tmpl create mode 100644 gui/slick/js/recommendedShows.js diff --git a/gui/slick/interfaces/default/home_addShows.tmpl b/gui/slick/interfaces/default/home_addShows.tmpl index 680cbf56..d64cab05 100644 --- a/gui/slick/interfaces/default/home_addShows.tmpl +++ b/gui/slick/interfaces/default/home_addShows.tmpl @@ -28,7 +28,17 @@

- + + +
Add Recommended Shows
+
+

Add Recommended Shows

+

For shows that you haven't downloaded yet, this option recommends shows to add based on your Trakt.tv watch list, creates a directory for its episodes, and adds it to SickRage. *** Trakt.tv must be enabled ***

+
+
+ +

+
Add Existing Shows
diff --git a/gui/slick/interfaces/default/home_recommendedShows.tmpl b/gui/slick/interfaces/default/home_recommendedShows.tmpl new file mode 100644 index 00000000..d6ccea86 --- /dev/null +++ b/gui/slick/interfaces/default/home_recommendedShows.tmpl @@ -0,0 +1,64 @@ +#import os.path +#import json +#import sickbeard +#set global $header="Recommended Shows" +#set global $title="Recommended Shows" + +#set global $sbPath="../.." + +#set global $statpath="../.."# +#set global $topmenu="home"# +#import os.path + +#include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_top.tmpl") + + + + + + + +#if $varExists('header') +

$header

+#else +

$title

+#end if + +
+
+ +
+
+ Select a recommended show + +
+

+
+
+ +
+ Pick the parent folder + +
+ #include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_rootDirs.tmpl") +
+
+ +
+ Customize options + +
+ #include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_addShowOptions.tmpl") +
+
+
+ +
+ +
+ +
+ + + +#include $os.path.join($sickbeard.PROG_DIR,"gui/slick/interfaces/default/inc_bottom.tmpl") diff --git a/gui/slick/js/addExistingShow.js b/gui/slick/js/addExistingShow.js index 18498488..f4d04767 100644 --- a/gui/slick/js/addExistingShow.js +++ b/gui/slick/js/addExistingShow.js @@ -37,7 +37,7 @@ $(document).ready(function() { $('.dir_check').each(function(i,w){ if ($(w).is(':checked')) { if (url.length) - url += '&' + url += '&'; url += 'rootDir=' + encodeURIComponent($(w).attr('id')); } }); diff --git a/gui/slick/js/recommendedShows.js b/gui/slick/js/recommendedShows.js new file mode 100644 index 00000000..d97fa773 --- /dev/null +++ b/gui/slick/js/recommendedShows.js @@ -0,0 +1,144 @@ +$(document).ready(function () { + function getRecommendedShows() { + $.getJSON(sbRoot + '/home/addShows/getRecommendedShows', {}, function (data) { + var firstResult = true; + var resultStr = '
\nRecommended Shows:\n'; + var checked = ''; + + if (data.results.length === 0) { + resultStr += 'No recommended shows found, update your watched shows list on trakt.tv.'; + } else { + $.each(data.results, function (index, obj) { + if (firstResult) { + checked = ' checked'; + firstResult = false; + } else { + checked = ''; + } + + var whichSeries = obj.join('|'); + + resultStr += ' '; + resultStr += '' + obj[2] + ''; + + if (obj[4] !== null) { + var startDate = new Date(obj[4]); + var today = new Date(); + if (startDate > today) { + resultStr += ' (will debut on ' + obj[4] + ')'; + } else { + resultStr += ' (started on ' + obj[4] + ')'; + } + } + + if (obj[0] !== null) { + resultStr += ' [' + obj[0] + ']'; + } + + if (obj[3] !== null) { + resultStr += '
' + obj[3]; + } + + resultStr += '


'; + }); + resultStr += ''; + } + resultStr += '

'; + $('#searchResults').html(resultStr); + updateSampleText(); + myform.loadsection(0); + }); + } + + $('#addShowButton').click(function () { + // if they haven't picked a show don't let them submit + if (!$("input:radio[name='whichSeries']:checked").val() && !$("input:hidden[name='whichSeries']").val().length) { + alert('You must choose a show to continue'); + return false; + } + + $('#recommendedShowsForm').submit(); + }); + + $('#qualityPreset').change(function () { + myform.loadsection(2); + }); + + var myform = new formtowizard({ + formid: 'recommendedShowsForm', + revealfx: ['slide', 500], + oninit: function () { + getRecommendedShows(); + updateSampleText(); + } + }); + + function goToStep(num) { + $('.step').each(function () { + if ($.data(this, 'section') + 1 == num) { + $(this).click(); + } + }); + } + + function updateSampleText() { + // if something's selected then we have some behavior to figure out + + var show_name, sep_char; + // if they've picked a radio button then use that + if ($('input:radio[name=whichSeries]:checked').length) { + show_name = $('input:radio[name=whichSeries]:checked').val().split('|')[2]; + } else { + show_name = ''; + } + + var sample_text = 'Adding show ' + show_name + ' into '; + + // if we have a root dir selected, figure out the path + if ($("#rootDirs option:selected").length) { + var root_dir_text = $('#rootDirs option:selected').val(); + if (root_dir_text.indexOf('/') >= 0) { + sep_char = '/'; + } else if (root_dir_text.indexOf('\\') >= 0) { + sep_char = '\\'; + } else { + sep_char = ''; + } + + if (root_dir_text.substr(sample_text.length - 1) != sep_char) { + root_dir_text += sep_char; + } + root_dir_text += '||' + sep_char; + + sample_text += root_dir_text; + } else if ($('#fullShowPath').length && $('#fullShowPath').val().length) { + sample_text += $('#fullShowPath').val(); + } else { + sample_text += 'unknown dir.'; + } + + sample_text += ''; + + // if we have a show name then sanitize and use it for the dir name + if (show_name.length) { + $.get(sbRoot + '/home/addShows/sanitizeFileName', {name: show_name}, function (data) { + $('#displayText').html(sample_text.replace('||', data)); + }); + // if not then it's unknown + } else { + $('#displayText').html(sample_text.replace('||', '??')); + } + + // also toggle the add show button + if (($("#rootDirs option:selected").length || ($('#fullShowPath').length && $('#fullShowPath').val().length)) && + ($('input:radio[name=whichSeries]:checked').length)) { + $('#addShowButton').attr('disabled', false); + } else { + $('#addShowButton').attr('disabled', true); + } + } + + $('#rootDirText').change(updateSampleText); + $('#whichSeries').live('change', updateSampleText); + +}); diff --git a/sickbeard/notifiers/trakt.py b/sickbeard/notifiers/trakt.py index d16f63c3..a4fb3f07 100644 --- a/sickbeard/notifiers/trakt.py +++ b/sickbeard/notifiers/trakt.py @@ -46,7 +46,7 @@ class TraktNotifier: # URL parameters data = { - 'indexer_id': ep_obj.show.indexerid, + 'tvdb_id': ep_obj.show.indexerid, 'title': ep_obj.show.name, 'year': ep_obj.show.startyear, 'episodes': [{ @@ -60,6 +60,26 @@ class TraktNotifier: if sickbeard.TRAKT_REMOVE_WATCHLIST: TraktCall("show/episode/unwatchlist/%API%", self._api(), self._username(), self._password(), data) + + def update_show_library(self, show_obj): + """ + Sends a request to trakt indicating that the given show and all its episodes is part of our library. + + show_obj: The TVShow object to add to trakt + """ + + if sickbeard.USE_TRAKT: + + # URL parameters + data = { + 'tvdb_id': show_obj.indexerid, + 'title': show_obj.name, + 'year': show_obj.startyear, + } + + if data is not None: + TraktCall("show/library/%API%", self._api(), self._username(), self._password(), data) + def test_notify(self, api, username, password): """ Sends a test notification to trakt with the given authentication info and returns a boolean diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index c6328e9a..2da67aa7 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -65,6 +65,7 @@ from lib.unrar2 import RarFile from lib import subliminal import tornado +from trakt import TraktCall try: import json @@ -1046,9 +1047,11 @@ class Manage(MainHandler): exceptions_list = [] - curErrors += Home(self.application, self.request).editShow(curShow, new_show_dir, anyQualities, bestQualities, exceptions_list, - new_flatten_folders, new_paused, subtitles=new_subtitles, anime=new_anime, - scene=new_scene, directCall=True) + curErrors += Home(self.application, self.request).editShow(curShow, new_show_dir, anyQualities, + bestQualities, exceptions_list, + new_flatten_folders, new_paused, + subtitles=new_subtitles, anime=new_anime, + scene=new_scene, directCall=True) if curErrors: logger.log(u"Errors: " + str(curErrors), logger.ERROR) @@ -1612,7 +1615,8 @@ class ConfigPostProcessing(MainHandler): wdtv_data=None, tivo_data=None, mede8er_data=None, keep_processed_dir=None, process_method=None, process_automatically=None, rename_episodes=None, airdate_episodes=None, unpack=None, - move_associated_files=None, nfo_rename=None, tv_download_dir=None, naming_custom_abd=None, naming_anime=None, + move_associated_files=None, nfo_rename=None, tv_download_dir=None, naming_custom_abd=None, + naming_anime=None, naming_abd_pattern=None, naming_strip_year=None, use_failed_downloads=None, delete_failed=None, extra_scripts=None, skip_removed_files=None, naming_custom_sports=None, naming_sports_pattern=None, autopostprocesser_frequency=None): @@ -2719,6 +2723,57 @@ class NewHomeAddShows(MainHandler): return _munge(t) + def recommendedShows(self, *args, **kwargs): + """ + Display the new show page which collects a tvdb id, folder, and extra options and + posts them to addNewShow + """ + t = PageTemplate(file="home_recommendedShows.tmpl") + t.submenu = HomeMenu() + + return _munge(t) + + def getRecommendedShows(self, *args, **kwargs): + final_results = [] + + if sickbeard.USE_TRAKT: + for myShow in sickbeard.showList: + notifiers.trakt_notifier.update_show_library(myShow) + + logger.log(u"Getting recommended shows from Trakt.tv", logger.DEBUG) + recommendedlist = TraktCall("recommendations/shows.json/%API%/" + sickbeard.TRAKT_USERNAME, + sickbeard.TRAKT_API, + sickbeard.TRAKT_USERNAME, sickbeard.TRAKT_PASSWORD) + if recommendedlist is None: + logger.log(u"Could not connect to trakt service, aborting recommended list update", logger.ERROR) + return + + map(final_results.append, ([int(show['tvdb_id']), show['url'], show['title'], show['overview'], + datetime.date.fromtimestamp(show['first_aired']).strftime('%Y%m%d')] for show in + recommendedlist if + not helpers.findCertainShow(sickbeard.showList, indexerid=int(show['tvdb_id'])))) + + return json.dumps({'results': final_results}) + + def addRecommendedShow(self, whichSeries=None, indexerLang="en", rootDir=None, defaultStatus=None, + anyQualities=None, bestQualities=None, flatten_folders=None, subtitles=None, + fullShowPath=None, other_shows=None, skipShow=None, providedIndexer=None, anime=None, + scene=None): + + indexer = 1 + indexer_name = sickbeard.indexerApi(int(indexer)).name + show_url = whichSeries.split('|')[1] + indexer_id = whichSeries.split('|')[0] + show_name = whichSeries.split('|')[2] + first_aired = whichSeries.split('|')[4] + + self.addNewShow('|'.join([indexer_name, str(indexer), show_url, indexer_id, show_name, first_aired]), + indexerLang, rootDir, + defaultStatus, + anyQualities, bestQualities, flatten_folders, subtitles, fullShowPath, other_shows, + skipShow, providedIndexer, anime, scene) + + return self.redirect('/home/') def existingShows(self, *args, **kwargs): """ @@ -3004,7 +3059,8 @@ class Home(MainHandler): self.set_header('Access-Control-Allow-Headers', 'x-requested-with') if sickbeard.started: - return callback + '(' + json.dumps({"msg": str(sickbeard.PID), "restarted": str(sickbeard.restarted)}) + ');' + return callback + '(' + json.dumps( + {"msg": str(sickbeard.PID), "restarted": str(sickbeard.restarted)}) + ');' else: return callback + '(' + json.dumps({"msg": "nope", "restarted": str(sickbeard.restarted)}) + ');'