1
0
mirror of https://github.com/moparisthebest/SickRage synced 2024-12-13 11:32:20 -05:00

Added trending shows feature, this allows you to choose from a wide selection of popular/trending shows with rating stats and easily add the show into SR by simply clicking the plus sign.

This commit is contained in:
echel0n 2014-07-01 01:49:12 -07:00
parent 9136df2ae5
commit 130daf7d0a
7 changed files with 232 additions and 32 deletions

92
gui/slick/css/trakt.css Normal file
View File

@ -0,0 +1,92 @@
.traktShowDiv {
clear: both;
border-left: 1px solid #CCCCCC;
border-right: 1px solid #CCCCCC;
border-bottom: 1px solid #CCCCCC;
margin: auto;
padding: 0px;
text-align: left;
width: 750px;
}
.traktShowDiv a, .traktShowDiv a:link, .traktShowDiv a:visited, .traktShowDiv a:hover {
text-decoration: none;
background: none;
}
.traktShowTitle a {
color: #000000;
float: left;
padding-top: 3px;
line-height: 1.2em;
font-size: 1.1em;
text-shadow: -1px -1px 0 #FFF);
}
.traktShowTitleIcons {
float: right;
padding: 3px 5px;
}
.traktShowDiv .title {
font-weight: 900;
color: #333;
}
.imgWrapper {
background: url("../images/loading.gif") no-repeat scroll center center #FFFFFF;
border: 3px solid #FFFFFF;
box-shadow: 1px 1px 2px 0 #555555;
float: left;
height: 50px;
overflow: hidden;
text-indent: -3000px;
width: 50px;
}
.imgWrapper .traktPosterThumb {
float: left;
min-height: 100%;
min-width: 100%;
width: 50px;
height: auto;
position: relative;
border: none;
vertical-align: middle;
}
.traktPosterThumb {
-ms-interpolation-mode: bicubic; /* make scaling look nicer for ie */
vertical-align: top;
height: auto;
width: 160px;
border-top: 1px solid #ccc;
border-right: 1px solid #ccc;
}
.traktShowDiv th {
color: #000;
letter-spacing: 1px;
text-align: left;
background-color: #333333;
}
.traktShowDiv th.nobg {
background: #efefef;
border-top: 1px solid #666;
text-align: center;
}
.traktShowDiv td {
border-top: 1px solid #d2ebe8;
background: #fff;
padding: 5px 10px 5px 10px;
color: #000;
}
.traktShowDiv td.trakts_show {
width: 100%;
height: 90%;
border-top: 1px solid #ccc;
vertical-align: top;
background: #F5FAFA;
color: #000;
}

View File

@ -26,15 +26,23 @@
<p>For shows that you haven't downloaded yet, this option finds a show on theTVDB.com and TVRage.com, creates a directory for its episodes, and adds it to SickRage.</p> <p>For shows that you haven't downloaded yet, this option finds a show on theTVDB.com and TVRage.com, creates a directory for its episodes, and adds it to SickRage.</p>
</div> </div>
</a> </a>
<br/><br/>
<a href="$sbRoot/home/addShows/trendingShows/" id="btnNewShow" class="btn btn-large">
<div class="button"><img src="$sbRoot/images/add-new32.png" height="32" width="32" alt="Add Trending Shows"/></div>
<div class="buttontext">
<h2>Add Trending Show</h2>
<p>For shows that you haven't downloaded yet, this option lets you choose from a list of current trending shows with ratings to add, creates a directory for its episodes, and adds it to SickRage.</p>
</div>
</a>
<br/><br/> <br/><br/>
#if $sickbeard.TRAKT_USE_RECOMMENDED: #if $sickbeard.TRAKT_USE_RECOMMENDED:
<a href="$sbRoot/home/addShows/recommendedShows/" id="btnNewShow" class="btn btn-large"> <a href="$sbRoot/home/addShows/recommendedShows/" id="btnNewShow" class="btn btn-large">
<div class="button"><img src="$sbRoot/images/add-new32.png" height="32" width="32" alt="Add Recommended Shows"/></div> <div class="button"><img src="$sbRoot/images/add-new32.png" height="32" width="32" alt="Add Recommended Shows"/></div>
<div class="buttontext"> <div class="buttontext">
<h2>Add Recommended Shows</h2> <h2>Add Recommended Show</h2>
<p>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 ***</p> <p>For shows that you haven't downloaded yet, this option recommends shows to add based on your Trakt.tv show library, creates a directory for its episodes, and adds it to SickRage. *** Trakt.tv must be enabled ***</p>
</div> </div>
</a> </a>
<br/><br/> <br/><br/>

View File

@ -0,0 +1,58 @@
#import sickbeard
#import datetime
#import re
#from sickbeard.common import *
#from sickbeard import sbdatetime
#set global $title="Trending Shows"
#set global $header="Trending Shows"
#set global $sbPath=".."
#set global $topmenu="comingEpisodes"
#import os.path
#include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_top.tmpl")
<script type="text/javascript" src="$sbRoot/js/plotTooltip.js?$sbPID"></script>
<link rel="stylesheet" type="text/css" href="$sbRoot/css/trakt.css?$sbPID" />
#if $varExists('header')
<h1 class="header">$header</h1>
#else
<h1 class="title">$title</h1>
#end if
<div class="traktShowDiv">
<table width="100%" cellspacing="1" border="0" cellpadding="0">
#for $i, $cur_show in $enumerate($trending_shows):
<div id="listing_${cur_show["tvdb_id"]}">
#if not $i%4
<tr>
#end if
<th style="background-color: #efefef;" valign="top">
<td class="trakt_show">
<a href="${cur_show["url"]}"><img alt="" class="traktPosterThumb" src="${cur_show["images"]["poster"]}" /></a>
<div class="clearfix">
<h2>$cur_show["ratings"]["percentage"]% <img src="$sbRoot/images/like.png"></h2>
<i>$cur_show["ratings"]["votes"] votes</i>
<div class="traktShowTitleIcons">
<a href="$sbRoot/home/addTraktShow?indexer_id=${cur_show["tvdb_id"]}&amp;showName=${cur_show["title"]}"><img alt="[add show]" height="16" width="16" src="$sbRoot/images/plus.png"></a>
</div>
</div>
</td>
</th>
</div>
#end for
</table>
</div>
<script type="text/javascript" charset="utf-8">
<!--
window.setInterval( "location.reload(true)", 600000); // Refresh every 10 minutes
//-->
</script>
#include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_bottom.tmpl")

View File

@ -7,7 +7,7 @@ try:
except ImportError: except ImportError:
from lib import simplejson as json from lib import simplejson as json
def TraktCall(method, api, username, password, data = {}): def TraktCall(method, api, username=None, password=None, data={}):
""" """
A generic method for communicating with trakt. Uses the method and data provided along A generic method for communicating with trakt. Uses the method and data provided along
with the auth info to send the command. with the auth info to send the command.
@ -26,19 +26,16 @@ def TraktCall(method, api, username, password, data = {}):
return None return None
# if the username isn't given then it failed # if the username isn't given then it failed
if not username: if username and password:
return None password = sha1(password).hexdigest()
data["username"] = username
password = sha1(password).hexdigest() data["password"] = password
# replace the API string with what we found # replace the API string with what we found
method = method.replace("%API%", api) method = method.replace("%API%", api)
data["username"] = username
data["password"] = password
# take the URL params and make a json object out of them # take the URL params and make a json object out of them
encoded_data = json.dumps(data); encoded_data = json.dumps(data)
# request the URL from trakt and parse the result as json # request the URL from trakt and parse the result as json
try: try:

View File

@ -432,7 +432,7 @@ IGNORE_WORDS = "german,french,core2hd,dutch,swedish,reenc,MrLss"
CALENDAR_UNPROTECTED = False CALENDAR_UNPROTECTED = False
TMDB_API_KEY = 'edc5f123313769de83a71e157758030b' TMDB_API_KEY = 'edc5f123313769de83a71e157758030b'
TRAKT_API_KEY = 'abd806c54516240c76e4ebc9c5ccf394'
__INITIALIZED__ = False __INITIALIZED__ = False
def initialize(consoleLogging=True): def initialize(consoleLogging=True):

View File

@ -147,9 +147,9 @@ class TraktChecker():
""" """
Adds a new show with the default settings Adds a new show with the default settings
""" """
showObj = helpers.findCertainShow(sickbeard.showList, int(indexerid)) if helpers.findCertainShow(sickbeard.showList, int(indexerid)):
if showObj != None:
return return
logger.log(u"Adding show " + str(indexerid)) logger.log(u"Adding show " + str(indexerid))
root_dirs = sickbeard.ROOT_DIRS.split('|') root_dirs = sickbeard.ROOT_DIRS.split('|')
location = root_dirs[int(root_dirs[0]) + 1] location = root_dirs[int(root_dirs[0]) + 1]
@ -161,6 +161,7 @@ class TraktChecker():
return return
else: else:
helpers.chmodAsParent(showPath) helpers.chmodAsParent(showPath)
sickbeard.showQueueScheduler.action.addShow(1, int(indexerid), showPath, status, sickbeard.showQueueScheduler.action.addShow(1, int(indexerid), showPath, status,
int(sickbeard.QUALITY_DEFAULT), int(sickbeard.QUALITY_DEFAULT),
int(sickbeard.FLATTEN_FOLDERS_DEFAULT)) int(sickbeard.FLATTEN_FOLDERS_DEFAULT))

View File

@ -48,7 +48,7 @@ from sickbeard import subtitles
from sickbeard import network_timezones from sickbeard import network_timezones
from sickbeard.providers import newznab, rsstorrent from sickbeard.providers import newznab, rsstorrent
from sickbeard.common import Quality, Overview, statusStrings, qualityPresetStrings, cpu_presets from sickbeard.common import Quality, Overview, statusStrings, qualityPresetStrings, cpu_presets, SKIPPED
from sickbeard.common import SNATCHED, UNAIRED, IGNORED, ARCHIVED, WANTED, FAILED from sickbeard.common import SNATCHED, UNAIRED, IGNORED, ARCHIVED, WANTED, FAILED
from sickbeard.common import SD, HD720p, HD1080p from sickbeard.common import SD, HD720p, HD1080p
from sickbeard.exceptions import ex from sickbeard.exceptions import ex
@ -82,6 +82,7 @@ from lib import adba
from Cheetah.Template import Template from Cheetah.Template import Template
from tornado.web import RequestHandler, HTTPError from tornado.web import RequestHandler, HTTPError
def authenticated(handler_class): def authenticated(handler_class):
def wrap_execute(handler_execute): def wrap_execute(handler_execute):
def basicauth(handler, transforms, *args, **kwargs): def basicauth(handler, transforms, *args, **kwargs):
@ -125,6 +126,7 @@ def authenticated(handler_class):
handler_class._execute = wrap_execute(handler_class._execute) handler_class._execute = wrap_execute(handler_class._execute)
return handler_class return handler_class
class HTTPRedirect(Exception): class HTTPRedirect(Exception):
"""Exception raised when the request should be redirected.""" """Exception raised when the request should be redirected."""
@ -138,9 +140,11 @@ class HTTPRedirect(Exception):
"""Use this exception as a request.handler (raise self).""" """Use this exception as a request.handler (raise self)."""
raise self raise self
def redirect(url, permanent=False, status=None): def redirect(url, permanent=False, status=None):
raise HTTPRedirect(url, permanent, status) raise HTTPRedirect(url, permanent, status)
@authenticated @authenticated
class MainHandler(RequestHandler): class MainHandler(RequestHandler):
def http_error_401_handler(self): def http_error_401_handler(self):
@ -216,7 +220,7 @@ class MainHandler(RequestHandler):
def get(self, *args, **kwargs): def get(self, *args, **kwargs):
try: try:
self.finish(self._dispatch()) self.finish(self._dispatch())
except HTTPRedirect,inst: except HTTPRedirect, inst:
self.redirect(inst.url, inst.permanent, inst.status) self.redirect(inst.url, inst.permanent, inst.status)
def post(self, *args, **kwargs): def post(self, *args, **kwargs):
@ -462,6 +466,7 @@ class MainHandler(RequestHandler):
browser = WebFileBrowser browser = WebFileBrowser
class PageTemplate(Template): class PageTemplate(Template):
def __init__(self, headers, *args, **KWs): def __init__(self, headers, *args, **KWs):
KWs['file'] = os.path.join(sickbeard.PROG_DIR, "gui/" + sickbeard.GUI_NAME + "/interfaces/default/", KWs['file'] = os.path.join(sickbeard.PROG_DIR, "gui/" + sickbeard.GUI_NAME + "/interfaces/default/",
@ -499,7 +504,7 @@ class PageTemplate(Template):
{'title': 'Manage', 'key': 'manage'}, {'title': 'Manage', 'key': 'manage'},
{'title': 'Config', 'key': 'config'}, {'title': 'Config', 'key': 'config'},
{'title': logPageTitle, 'key': 'errorlogs'}, {'title': logPageTitle, 'key': 'errorlogs'},
] ]
class IndexerWebUI(MainHandler): class IndexerWebUI(MainHandler):
@ -518,6 +523,7 @@ class IndexerWebUI(MainHandler):
def _munge(string): def _munge(string):
return unicode(string).encode('utf-8', 'xmlcharrefreplace') return unicode(string).encode('utf-8', 'xmlcharrefreplace')
def _getEpisode(show, season=None, episode=None, absolute=None): def _getEpisode(show, season=None, episode=None, absolute=None):
if show is None: if show is None:
return "Invalid show parameters" return "Invalid show parameters"
@ -2643,7 +2649,7 @@ class NewHomeAddShows(MainHandler):
'display_dir': '<b>' + ek.ek(os.path.dirname, cur_path) + os.sep + '</b>' + ek.ek( 'display_dir': '<b>' + ek.ek(os.path.dirname, cur_path) + os.sep + '</b>' + ek.ek(
os.path.basename, os.path.basename,
cur_path), cur_path),
} }
# see if the folder is in XBMC already # see if the folder is in XBMC already
dirResults = myDB.select("SELECT * FROM tv_shows WHERE location = ?", [cur_path]) dirResults = myDB.select("SELECT * FROM tv_shows WHERE location = ?", [cur_path])
@ -2745,10 +2751,10 @@ class NewHomeAddShows(MainHandler):
logger.log(u"Could not connect to trakt service, aborting recommended list update", logger.ERROR) logger.log(u"Could not connect to trakt service, aborting recommended list update", logger.ERROR)
return return
map(final_results.append, ([int(show['tvdb_id']), show['url'], show['title'], show['overview'], map(final_results.append,
datetime.date.fromtimestamp(show['first_aired']).strftime('%Y%m%d')] for show in ([int(show['tvdb_id']), show['url'], show['title'], show['overview'],
recommendedlist if datetime.date.fromtimestamp(int(show['first_aired']) / 1000.0).strftime('%Y%m%d')] for show in
not helpers.findCertainShow(sickbeard.showList, indexerid=int(show['tvdb_id'])))) recommendedlist if not helpers.findCertainShow(sickbeard.showList, indexerid=int(show['tvdb_id']))))
return json.dumps({'results': final_results}) return json.dumps({'results': final_results})
@ -2764,10 +2770,22 @@ class NewHomeAddShows(MainHandler):
show_name = whichSeries.split('|')[2] show_name = whichSeries.split('|')[2]
return self.addNewShow('|'.join([indexer_name, str(indexer), show_url, indexer_id, show_name, ""]), return self.addNewShow('|'.join([indexer_name, str(indexer), show_url, indexer_id, show_name, ""]),
indexerLang, rootDir, indexerLang, rootDir,
defaultStatus, defaultStatus,
anyQualities, bestQualities, flatten_folders, subtitles, fullShowPath, other_shows, anyQualities, bestQualities, flatten_folders, subtitles, fullShowPath, other_shows,
skipShow, providedIndexer, anime, scene) skipShow, providedIndexer, anime, scene)
def trendingShows(self, *args, **kwargs):
"""
Display the new show page which collects a tvdb id, folder, and extra options and
posts them to addNewShow
"""
t = PageTemplate(headers=self.request.headers, file="home_trendingShows.tmpl")
t.submenu = HomeMenu()
t.trending_shows = TraktCall("shows/trending.json/%API%/", sickbeard.TRAKT_API_KEY)
return _munge(t)
def existingShows(self, *args, **kwargs): def existingShows(self, *args, **kwargs):
""" """
@ -2778,6 +2796,33 @@ class NewHomeAddShows(MainHandler):
return _munge(t) return _munge(t)
def addTraktShow(self, indexer_id, showName):
if helpers.findCertainShow(sickbeard.showList, int(indexer_id)):
return
root_dirs = sickbeard.ROOT_DIRS.split('|')
location = root_dirs[int(root_dirs[0]) + 1]
show_dir = ek.ek(os.path.join, location, helpers.sanitizeFileName(showName))
dir_exists = helpers.makeDir(show_dir)
if not dir_exists:
logger.log(u"Unable to create the folder " + show_dir + ", can't add the show", logger.ERROR)
return
else:
helpers.chmodAsParent(show_dir)
sickbeard.showQueueScheduler.action.addShow(1, int(indexer_id), show_dir,
default_status=sickbeard.STATUS_DEFAULT,
quality=sickbeard.QUALITY_DEFAULT,
flatten_folders=sickbeard.FLATTEN_FOLDERS_DEFAULT,
subtitles=sickbeard.SUBTITLES_DEFAULT,
anime=sickbeard.ANIME_DEFAULT,
scene=sickbeard.SCENE_DEFAULT)
ui.notifications.message('Show added', 'Adding the specified show into ' + show_dir)
# done adding show
redirect('/home/')
def addNewShow(self, whichSeries=None, indexerLang="en", rootDir=None, defaultStatus=None, def addNewShow(self, whichSeries=None, indexerLang="en", rootDir=None, defaultStatus=None,
anyQualities=None, bestQualities=None, flatten_folders=None, subtitles=None, anyQualities=None, bestQualities=None, flatten_folders=None, subtitles=None,
@ -3394,7 +3439,7 @@ class Home(MainHandler):
return _munge(t) return _munge(t)
else: else:
return self._genericMessage("Update Failed", return self._genericMessage("Update Failed",
"Update wasn't successful, not restarting. Check your log for more information.") "Update wasn't successful, not restarting. Check your log for more information.")
def displayShow(self, show=None): def displayShow(self, show=None):
@ -3999,7 +4044,6 @@ class Home(MainHandler):
myDB = db.DBConnection() myDB = db.DBConnection()
myDB.mass_action(sql_l) myDB.mass_action(sql_l)
if int(status) == WANTED: if int(status) == WANTED:
msg = "Backlog was automatically started for the following seasons of <b>" + showObj.name + "</b>:<br />" msg = "Backlog was automatically started for the following seasons of <b>" + showObj.name + "</b>:<br />"
for season in segment: for season in segment:
@ -4294,9 +4338,9 @@ class Home(MainHandler):
return json.dumps({'result': 'failure'}) return json.dumps({'result': 'failure'})
class UI(MainHandler): class UI(MainHandler):
def add_message(self): def add_message(self):
ui.notifications.message('Test 1', 'This is test number 1') ui.notifications.message('Test 1', 'This is test number 1')
ui.notifications.error('Test 2', 'This is test number 2') ui.notifications.error('Test 2', 'This is test number 2')
@ -4307,8 +4351,8 @@ class UI(MainHandler):
cur_notification_num = 1 cur_notification_num = 1
for cur_notification in ui.notifications.get_notifications(self.request.remote_ip): for cur_notification in ui.notifications.get_notifications(self.request.remote_ip):
messages['notification-' + str(cur_notification_num)] = {'title': cur_notification.title, messages['notification-' + str(cur_notification_num)] = {'title': cur_notification.title,
'message': cur_notification.message, 'message': cur_notification.message,
'type': cur_notification.type} 'type': cur_notification.type}
cur_notification_num += 1 cur_notification_num += 1
return json.dumps(messages) return json.dumps(messages)