Merge branch 'feature/AddTraktCollection' into develop

This commit is contained in:
JackDandy 2015-12-22 13:04:45 +00:00
commit 1cf0b5f9b2
29 changed files with 982 additions and 291 deletions

View file

@ -64,17 +64,22 @@
* Change increase frequency of updating show data
* Remove Animenzb provider
* Change increase the scope and number of non release group text that is identified and removed
* Add a general config setting to allow adding incomplete show data
* Add general config setting to allow adding incomplete show data
* Change to throttle connection rate on thread initiation for adba library
* Change default manage episodes selector to Snatched episodes if items exist else Wanted on Episode Status Manage page
* Change snatched row colour on Episode Status Manage page to match colour used on the show details page
* Change replace trakt with libtrakt for API v2
* Change Trakt notification config to only handle PIN authentication with the service
* Change improve robustness of Trakt communications
* Change Trakt notification config to use PIN authentication with the service
* Add multiple Trakt account support to Config/Notifications/Social
* Add setting to Trakt notification to update collection with downloaded episode info
* Change trakt notifier logo
* Remove all other Trakt deprecated API V1 service features pending reconsideration
* Change increase show search capability when using plain text and also add TVDB id, IMDb id and IMDb url search
* Change improve existing show page and the handling when an attempt to add a show to an existing location
* Change consolidate Trakt Trending and Recommended views into an "Add From Trakt" view which defaults to trending
* Change Trakt view drop down "Show" to reveal Brand-new Shows, Season Premieres, Recommendations and Trending views
* Change Add from Trakt/"Shows:" with Anticipated, New Seasons, New Shows, Popular, Recommendations, and Trending views
* Change Add from Trakt/"Shows:" with Most Watched, Collected during the last month on Trakt
* Change add season info to "Show: Trakt New Seasons" view on the Add from Trakt page
* Change increase number of displayed Trakt shows to 100
* Add genres and rating to all Trakt shows
@ -102,6 +107,8 @@
* Change to always display branch and commit hash on 'Help & Info' page
* Add option to create season search exceptions from editShow page
* Change sab to use requests library
* Add "View Changes" to tools menu
* Change disable connection attempts and remove UI references to the TVRage info source
### 0.10.0 (2015-08-06 11:05:00 UTC)

View file

@ -528,6 +528,15 @@ h2.day, h2.network{
border-color:#8DBEEE
}
/* =======================================================================
viewchanges.tmpl
========================================================================== */
#changes{
color:rgb(255,255,255);
background-color:rgb(61,61,61);
border:1px solid rgb(17,17,17)
}
/* =======================================================================
config*.tmpl
========================================================================== */
@ -626,6 +635,14 @@ div.metadataDiv .disabled{
background:url("../images/warning16.png") no-repeat right 5px center #fff
}
.solid-border{
border:1px solid #555
}
.solid-border-top{
border-top:1px solid #555
}
/* =======================================================================
manage*.tmpl
========================================================================== */
@ -1181,7 +1198,7 @@ browser.css
#fileBrowserDialog ul li a:hover{
color:#09a2ff;
background:none
background: rgb(61, 61, 61) none
}
.ui-menu .ui-menu-item{
@ -1215,6 +1232,10 @@ div.stepsguide .step p{
color:#646464
}
#newShowPortal #addShowForm .stepsguide .disabledstep:hover > .smalltext{
color:#ccc;
}
div.stepsguide .disabledstep p{
border-color:#1178B3
}

View file

@ -510,6 +510,15 @@ h2.day, h2.network{
border-color:#C7DB40
}
/* =======================================================================
viewchanges.tmpl
========================================================================== */
#changes{
color:rgb(51,51,51);
background-color:rgb(245,245,245);
border:1px solid rgb(204,204,204)
}
/* =======================================================================
config*.tmpl
========================================================================== */
@ -601,6 +610,14 @@ div.metadataDiv .disabled{
background:url("../images/warning16.png") no-repeat right 5px center #fff
}
.solid-border{
border:1px solid #ccc
}
.solid-border-top{
border-top:1px solid #ccc
}
/* =======================================================================
manage*.tmpl
========================================================================== */
@ -1143,7 +1160,7 @@ browser.css
#fileBrowserDialog ul li a:hover{
color:#00f;
background:none
background: rgb(220, 220, 220) none
}
.ui-menu .ui-menu-item{
@ -1181,6 +1198,10 @@ div.stepsguide .disabledstep p{
border-color:#8a775e
}
#newShowPortal #addShowForm .stepsguide .disabledstep:hover > .smalltext{
color:#8a775e;
}
div.formpaginate .prev, div.formpaginate .next{
color:#fff;
background:#57442b

View file

@ -2103,6 +2103,36 @@ td.col-cache{
width:20px
}
/* =======================================================================
viewchanges.tmpl
========================================================================== */
#changes{
display:block;
padding:9.5px;
border-radius:4px 4px 4px 4px;
font:12px/13px "Open Sans",verdana,sans-serif
}
#changes .release{
margin:15px 5px 6px 0;
padding-bottom:3px;
border-bottom:1px solid gray
}
#changes .ver{font:16px/17px "Open Sans",verdana,sans-serif;margin-right:0.2em;}
#changes .old{padding-top:15px}
#changes div{margin:0 0 8px}
#changes .btn-text{width:5em;margin-right:0.2em;float:left}
#changes .change-text{display:block;margin-left:5.5em;padding-top:2px}
.change-add{background-color:rgb(63,127,0)}
.change-change{background-color:rgb(91,153,13)}
.change-fix{background-color:rgb(38,114,182)}
.change-port{background-color:rgb(102,102,102)}
.change-remove{}
.change-update{background-color:rgb(98,25,147)}
/* =======================================================================
config*.tmpl
@ -2529,6 +2559,32 @@ div.metadataDiv .disabled{
margin:6px 4px 0 0
}
#trakt-collection th,#trakt-collection td{
padding:3px 5px
}
#trakt-collection .col-1{
text-align:left
}
#trakt-collection th,#trakt-collection td.opt{
text-align:center
}
#trakt-collection .col-1{
width:192px
}
#config #trakt-collection input{
float:none;
margin:0;
vertical-align:middle
}
#config .trakt.component-desc{
margin-left:0
}
/* =======================================================================
manage*.tmpl
========================================================================== */
@ -2630,6 +2686,7 @@ span.path{
line-height:18px
}
span.btn-text,
span.quality{
font:12px/13px "Open Sans", verdana, sans-serif;
background-image:-webkit-linear-gradient(top, rgba(255, 255, 255, 0.08),rgba(255, 255, 255, 0) 50%,rgba(0, 0, 0, 0) 50%,rgba(0, 0, 0, 0.25));

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -122,22 +122,22 @@
</span>
</label>
</div>
#if 1 < $len($indexers)
<div class="field-pair">
<label for="indexer_default">
<span class="component-title">Use initial indexer set to</span>
<span class="component-desc">
<select id="indexer_default" name="indexer_default" class="form-control input-sm">
<option value="0"#echo ('', $selected)[0 == $indexer]#>All Indexers</option>
#for $indexer in $sickbeard.indexerApi().indexers
#for $indexer in $indexers
<option value="$indexer"#echo ('', $selected)[$indexer == $sickbeard.INDEXER_DEFAULT]#>$sickbeard.indexerApi().indexers[$indexer]</option>
#end for
#end for
</select>
<span>as the default selection when adding new shows</span>
</span>
</label>
</div>
#end if
<div class="field-pair">
<label for="indexer_timeout">
<span class="component-title">Timeout show indexer at</span>
@ -636,7 +636,7 @@
<span class="component-title">Use proxy for indexers</span>
<span class="component-desc">
<input type="checkbox" name="proxy_indexers" id="proxy_indexers"#echo ('', $checked)[True == $sickbeard.PROXY_INDEXERS]#>
<p>use proxy host for connecting to indexers (thetvdb, tvrage)</p>
<p>use proxy host for TV info source connections</p>
</span>
</label>
</div>

View file

@ -1,3 +1,4 @@
#import base64
#import sickbeard
#import re
#from lib.libtrakt import TraktAPI
@ -13,9 +14,9 @@
<script type="text/javascript" src="$sbRoot/js/configNotifications.js?v=$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/config.js?v=$sbPID"></script>
#if $varExists('header')
#if $varExists('header')
<h1 class="header">$header</h1>
#else
#else
<h1 class="title">$title</h1>
#end if
@ -155,7 +156,7 @@
</div><!-- /xbmc component-group //-->
<div class="component-group">
<div class="component-group">
<div class="component-group-desc">
<img class="notifier-icon" src="$sbRoot/images/notifiers/kodi.png" alt="" title="Kodi" />
<h3><a href="<%= anon_url('http://kodi.tv/') %>" rel="noreferrer" onclick="window.open(this.href, '_blank'); return false;">Kodi</a></h3>
@ -275,7 +276,7 @@
</div><!-- /content_use_kodi //-->
</fieldset>
</div><!-- /kodi component-group //-->
<div class="component-group">
<div class="component-group-desc">
<img class="notifier-icon" src="$sbRoot/images/notifiers/plex.png" alt="" title="Plex Media Server" />
@ -1481,16 +1482,77 @@
<div id="content_use_trakt">
<div class="field-pair">
<label for="trakt_accounts">
<span class="component-title">Trakt account (status):</span>
<span class="component-desc">
<select name="trakt_accounts" id="trakt_accounts" class="pull-left form-control input-sm">
<option value="new" selected="selected">Add account</option>
#set $trakt_accounts = $sickbeard.TRAKT_ACCOUNTS
#for $void, $account in $trakt_accounts.items()
<option value="$account.account_id">$account.account_id - $account.name #if $account.active then '(ok)' else '(inactive)'#</option>
#end for
</select>
<input type="button" class="btn" value="Delete" id="trakt-delete" disabled="disabled" />
</span>
</label>
<label for="trakt_pin">
<span class="component-title">Trakt PIN:</span>
<span class="component-desc">
<input type="text" name="trakt_pin" id="trakt_pin" value="" class="form-control input-sm input250" />
<input type="button" class="btn" value="Connect" id="trakt-authenticate" />
<div class="clear-left"><p>get your PIN at: <a href="<%= anon_url(sickbeard.TRAKT_PIN_URL) %>" rel="noreferrer" onclick="window.open(this.href, '_blank'); return false;"><b>$sickbeard.TRAKT_PIN_URL</b></a></p></div>
<div class="clear-left"><p>get an active PIN using: <a href="<%= anon_url(sickbeard.TRAKT_PIN_URL) %>" rel="noreferrer" onclick="window.open(this.href, '_blank'); return false;"><b>$sickbeard.TRAKT_PIN_URL</b></a>
<br />tip: easily add accounts by using the link in other browsers "signed in" to Trakt
</p></div>
</span>
</label>
<div class="testNotification" id="trakt-authentication-result"></div>
</div>
<div class="field-pair">
<span class="trakt component-desc" style="width:100%">
#set num_accounts = len($trakt_accounts)
#set $num_columns = (1, num_accounts)[1 < num_accounts]
<table id="trakt-collection" class="solid-border" cellpadding="0" cellspacing="0" border="0">
<thead>
<tr>
<th class="col-1" style="font-size:12px;font-weight:normal" rowspan="2"><i>Update multiple accounts with downloaded episode info</i></th>
<th colspan="$num_columns">#echo not len($trakt_accounts) and '<i>Connect New Pin</i>' or 1 < len($trakt_accounts) and 'Trakt accounts' or 'Account'#</th>
</tr>
<tr>
#if not len($trakt_accounts)
<th>..</th>
#end if
#for $void, $account in $trakt_accounts.items()
<th class="tid-$account.account_id">$account.name#if $account.active then '' else '<br />(inactive)'#</th>
#end for
</tr>
</thead>
<tbody>
#if not $root_dirs:
#set $root_dirs = [{'root_def': False, 'loc': 'all folders. Multiple parent folders will appear here.', 'b64': ''}]
#end if
#for $root_info in $root_dirs:
<tr class="solid-border-top">
<td class="col-1"><span style="font-size:13px;font-weight:bold" data-loc="$root_info['b64']">Update collection</span></td>
#if not len($trakt_accounts)
<td class="opt">..</td>
#end if
#for $void, $account in $trakt_accounts.items()
#set $cur_selected = ('', ' checked="checked"')[$root_info['loc'] in $sickbeard.TRAKT_UPDATE_COLLECTION.get($account.account_id, '')]
#set $id_loc = "update_trakt_%s_%s" % ($account.account_id, $root_info['b64'])
<td class="opt">
<input type="checkbox" id="$id_loc" name="$id_loc"$cur_selected />
</td>
#end for
</tr>
<tr>
<td colspan="${1 + $num_columns}">for #if $root_info['root_def'] then '*' else ''#$root_info['loc']</td>
</tr>
#end for
</tbody>
</table>
</span>
</div>
<!--
<div class="field-pair">
<label for="trakt_default_indexer">
@ -1725,4 +1787,4 @@
//-->
</script>
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl')
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl')

View file

@ -128,10 +128,18 @@
#if 'Trakt' == $browse_type
#set $mode = $kwargs and $kwargs.get('mode', None)
#set $selected = ' class="selected"'
<option value="traktTrending"#echo ('', selected)['trending' == $mode]#>Trakt Trending</option>
<option value="traktNewShows"#echo ('', selected)['newshows' == $mode]#>Trakt New Shows</option>
<option value="traktAnticipated"#echo ('', selected)['anticipated' == $mode]#>Trakt Anticipating</option>
<option value="traktNewSeasons"#echo ('', selected)['newseasons' == $mode]#>Trakt New Seasons</option>
<option value="traktRecommended"#echo ('', selected)['recommended' == $mode]#>Trakt Recommended</option>
<option value="traktNewShows"#echo ('', selected)['newshows' == $mode]#>Trakt New Shows</option>
<option value="traktPopular"#echo ('', selected)['popular' == $mode]#>Trakt Popular</option>
<option value="traktTrending"#echo ('', selected)['trending' == $mode]#>Trakt Trending</option>
<option value="traktWatched"#echo ('', selected)['watched' == $mode]#>Trakt Most Watched (Last Month)</option>
<option value="traktCollected"#echo ('', selected)['collected' == $mode]#>Trakt Most Collected (Last Month)</option>
#for $account in $sickbeard.TRAKT_ACCOUNTS
#if $sickbeard.TRAKT_ACCOUNTS[$account].active and $sickbeard.TRAKT_ACCOUNTS[$account].name
<option value="traktRecommended?account=$account"#echo ('', selected)['recommended-%s' % $account == $mode]#>Trakt Recommended for $sickbeard.TRAKT_ACCOUNTS[$account].name</option>
#end if
#end for
#end if
</select>

View file

@ -31,13 +31,16 @@
#set $indexer = $curDir['existing_info'][2]
#end if
#set $indexer = $sickbeard.INDEXER_DEFAULT
#*
#set $indexer = 0
#if $curDir['existing_info'][0]
#set $indexer = $curDir['existing_info'][2]
#elif 0 < $sickbeard.INDEXER_DEFAULT
#set $indexer = $sickbeard.INDEXER_DEFAULT
#end if
*#
<tr>
<td class="col-checkbox">
<input type="checkbox" id="$show_id" class="dirCheck" checked=checked>
@ -58,7 +61,9 @@
<td align="center">
<select name="indexer">
#for $curIndexer in $sickbeard.indexerApi().indexers.items()
#if $curIndexer[0] == $sickbeard.INDEXER_DEFAULT
<option value="$curIndexer[0]" #if $curIndexer[0] == $indexer then 'selected="selected"' else ''#>$curIndexer[1]</option>
#end if
#end for
</select>
</td>

View file

@ -49,20 +49,29 @@
<input type="hidden" id="providedName" value="$provided_indexer_name" />
<input type="hidden" id="providedIndexer" value="$provided_indexer" />
#else
#if 2 > $len($indexers)
<style>
#addShowForm input#nameToSearch{width:611px}
</style>
<input type="hidden" id="providedIndexer" value="$provided_indexer" />
#end if
<input type="text" id="nameToSearch" value="$default_show_name" class="form-control form-control-inline input-sm input350" />
&nbsp;
<span style="float:right">
<select name="indexerLang" id="indexerLangSelect" class="form-control form-control-inline input-sm">
<option value="en" selected="selected">en</option>
</select><b>&nbsp;*</b>
<select name="providedIndexer" id="providedIndexer" class="form-control form-control-inline input-sm">
<option value="0" #if $provided_indexer == 0 then "selected=\"selected\"" else ""#>All Indexers</option>
#for $indexer in $indexers
<option value="$indexer" #if $provided_indexer == $indexer then "selected=\"selected\"" else ""#>$indexers[$indexer]</option>
#end for
</select>
&nbsp;
<input class="btn btn-inline" type="button" id="searchName" value="Search" />
<select name="indexerLang" id="indexerLangSelect" class="form-control form-control-inline input-sm">
<option value="en" selected="selected">en</option>
</select><b>&nbsp;*</b>
#if 1 < $len($indexers)
<select name="providedIndexer" id="providedIndexer" class="form-control form-control-inline input-sm">
<option value="0" #if $provided_indexer == 0 then "selected=\"selected\"" else ""#>All Indexers</option>
#for $indexer in $indexers
<option value="$indexer" #if $provided_indexer == $indexer then "selected=\"selected\"" else ""#>$indexers[$indexer]</option>
#end for
</select>
#end if
&nbsp;
<input class="btn btn-inline" type="button" id="searchName" value="Search" />
</span>
<br />
<p style="margin:5px 0 15px">Enter show name, TVDB ID, IMDb Url, or IMDb ID.&nbsp;&nbsp;<b>*</b>SickGear supports english, language is used for show/episode data</p>

View file

@ -168,9 +168,11 @@
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" data-delay="0" tabindex="$tab#set $tab += 1#"><img src="$sbRoot/images/menu/system18-2.png" class="navbaricon hidden-xs" /><b class="caret hidden-xs"></b><span class="visible-xs">System <b class="caret"></b></span></a>
<ul class="dropdown-menu">
<li><a href="$sbRoot/manage/manageSearches/forceVersionCheck" tabindex="$tab#set $tab += 1#"><i class="sgicon-updatecheck"></i>Force Version Check</a></li>
<li><a href="$sbRoot/manage/manageSearches/forceVersionCheck" tabindex="$tab#set $tab += 1#"><i class="sgicon-updatecheck"></i>Check for Updates</a></li>
<li><a href="$sbRoot/home/viewchanges" tabindex="$tab#set $tab += 1#"><i class="sgicon-log"></i>View Changes</a></li>
<li class="divider"></li>
#if $sickbeard.WEB_USERNAME or $sickbeard.WEB_PASSWORD
<li><a href="$sbRoot/logout" class="confirm logout" tabindex="$tab#set $tab += 1#"><i class="sgicon-logout"></i>Logout</a></li>
<li><a href="$sbRoot/logout" class="confirm logout" tabindex="$tab#set $tab += 1#"><i class="sgicon-logout"></i>Logout</a></li>
#end if
<li><a href="$sbRoot/home/restart/?pid=$sbPID" class="confirm restart" tabindex="$tab#set $tab += 1#"><i class="sgicon-restart"></i>Restart</a></li>
<li><a href="$sbRoot/home/shutdown/?pid=$sbPID" class="confirm shutdown" tabindex="$tab#set $tab += 1#"><i class="sgicon-shutdown"></i>Shutdown</a></li>

View file

@ -0,0 +1,20 @@
#import sickbeard
##
#set global $header = 'Changes'
#set global $title = 'Changes'
#set global $topmenu = ''
##
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
<h1 class="header">$header</h1>
<div class="align-left" style="margin:30px 0">
<div id="changes">
#for $i, $change in $enumerate($changelist)##if 'rel' == $change['type']#
<div class="release#echo ('', ' old')[bool($i)]#"><span class="ver">$change['ver']</span> <span class="change-date grey-text">$change['date']</span></div>
#else# <div><span class="btn-text change-$change['type'].lower()">$change['type']</span> <span class="change-text">$change['text']</span></div>
#end if##end for#
</div>
</div>
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_bottom.tmpl')

View file

@ -1,4 +1,4 @@
$(document).ready(function(){
$(document).ready(function(){
var loading = '<img src="' + sbRoot + '/images/loading16' + themeSpinner + '.gif" height="16" width="16" />';
$('#testGrowl').click(function () {
@ -353,28 +353,162 @@ $(document).ready(function(){
});
var elTraktAuth = $('#trakt-authenticate'), elTraktAuthResult = $('#trakt-authentication-result');
elTraktAuth.click(function() {
var elTrakt = $('#trakt_pin'), traktPin = $.trim(elTrakt.val());
if(!traktPin) {
elTrakt.addClass('warning');
function trakt_send_auth(){
var elAccountSelect = $('#trakt_accounts'), strCurAccountId = elAccountSelect.find('option:selected').val(),
elTraktPin = $('#trakt_pin'), strPin = $.trim(elTraktPin.val());
elTraktAuthResult.html(loading);
$.get(sbRoot + '/home/trakt_authenticate', {'pin': strPin, 'account': strCurAccountId})
.done(function(data) {
elTraktAuth.prop('disabled', !1);
elTraktPin.val('');
var JSONData = $.parseJSON(data);
elTraktAuthResult.html('Success' == JSONData.result
? JSONData.result + ' account: ' + JSONData.account_name
: JSONData.result + ' ' + JSONData.error_message);
if ('Success' == JSONData.result) {
var elUpdateRows = $('#trakt-collection').find('tr');
if ('new' == strCurAccountId) {
elAccountSelect.append($('<option>', {value: JSONData.account_id, text: JSONData.account_id + ' - ' + JSONData.account_name + ' (ok)'}));
if ('Connect New Pin' == elUpdateRows.eq(0).find('th').last().text()) {
elUpdateRows.eq(0).find('th').last().html('Account');
elUpdateRows.eq(1).find('th').last().html(JSONData.account_name);
elUpdateRows.eq(1).find('th').last().addClass('tid-' + JSONData.account_id);
elUpdateRows.has('td').each(function(nRow) {
var elCells = $(this).find('td');
if (!(nRow % 2)) {
var IdLoc = 'update_trakt_' + JSONData.account_id + '_' + elCells.eq(0).find('span').attr('data-loc');
elCells.last().html('<input type="checkbox" id="' + IdLoc + '" name="' + IdLoc + '">');
} else {
elCells.attr('colspan', 1);
}
});
}
else
{
elUpdateRows.eq(0).find('th').last().html('Trakt accounts');
elUpdateRows.eq(0).find('th').last().attr('colspan', 1 + parseInt(elUpdateRows.eq(0).find('th').last().attr('colspan'), 10));
elUpdateRows.eq(1).find('th').last().after('<th>' + JSONData.account_name + '</th>');
elUpdateRows.eq(1).find('th').last().addClass('tid-' + JSONData.account_id);
elUpdateRows.has('td').each(function(nRow) {
var elCells = $(this).find('td');
if (!(nRow % 2)) {
var IdLoc = 'update_trakt_' + JSONData.account_id + '_' + elCells.eq(0).find('span').attr('data-loc');
elCells.last().after('<td class="opt"><input type="checkbox" id="' + IdLoc + '" name="' + IdLoc + '"></td>');
} else {
elCells.attr('colspan', 1 + parseInt(elCells.attr('colspan'), 10));
}
});
}
}
else
{
elAccountSelect.find('option[value=' + strCurAccountId + ']').html(JSONData.account_id + ' - ' + JSONData.account_name + ' (ok)');
elUpdateRows.eq(1).find('th[class*="tid-' + JSONData.account_id + '"]').text(JSONData.account_name);
}
}
});
}
elTraktAuth.click(function(e) {
var elTraktPin = $('#trakt_pin');
elTraktPin.removeClass('warning');
if (!$.trim(elTraktPin.val())) {
elTraktPin.addClass('warning');
elTraktAuthResult.html('Please enter a required PIN above.');
} else {
elTrakt.removeClass('warning');
$(this).prop('disabled', true);
elTraktAuthResult.html(loading);
$.get(sbRoot + '/home/trakt_authenticate', {'pin': traktPin})
.done(function(data) {
elTraktAuthResult.html(data);
elTraktAuth.prop('disabled', false);
var elAccountSelect = $('#trakt_accounts'), elSelected = elAccountSelect.find('option:selected');
$(this).prop('disabled', !0);
if ('new' != elSelected.val()) {
$.confirm({
'title' : 'Replace Trakt Account',
'message' : 'Are you sure you want to replace <span class="footerhighlight">' + elSelected.text() + '</span> ?<br /><br />',
'buttons' : {
'Yes' : {
'class' : 'green',
'action': function() {
trakt_send_auth();
}
},
'No' : {
'class' : 'red',
'action': function() {
e.preventDefault();
elTraktAuth.prop('disabled', !1);
}
}
}
});
}
else
{
trakt_send_auth();
}
}
});
elTraktAuthResult.html(loading);
$.get(sbRoot + '/home/trakt_get_connected_account')
.done(function(data) {
elTraktAuthResult.html(data);
$('#trakt_accounts').change(function() {
$('#trakt-delete').prop('disabled', 'new' == $('#trakt_accounts').val());
});
$('#trakt-delete').click(function(e) {
var elAccountSelect = $('#trakt_accounts'), elSelected = elAccountSelect.find('option:selected'), that = $(this);
that.prop('disabled', !0);
$.confirm({
'title' : 'Remove Trakt Account',
'message' : 'Are you sure you want to remove <span class="footerhighlight">' + elSelected.text() + '</span> ?<br /><br />',
'buttons' : {
'Yes' : {
'class' : 'green',
'action': function() {
$.get(sbRoot + '/home/trakt_delete', {'accountid': elSelected.val()})
.done(function(data) {
that.prop('disabled', !1);
var JSONData = $.parseJSON(data);
if ('Success' == JSONData.result) {
var elCollection = $('#trakt-collection'), elUpdateRows = elCollection.find('tr'),
header = elCollection.find('th[class*="tid-' + JSONData.account_id + '"]'),
num_acc = parseInt(JSONData.num_accounts, 10);
elUpdateRows.eq(0).find('th').last().html(!num_acc && '<i>Connect New Pin</i>' ||
(1 < num_acc ? 'Trakt accounts' : 'Account'));
elUpdateRows.find('th[colspan]').attr('colspan', 1 < num_acc ? num_acc : 1);
!num_acc && header.html('..') || header.remove();
var elInputs = elUpdateRows.find('input[id*=update_trakt_' + JSONData.account_id + ']');
!num_acc && elInputs.parent().html('..') || elInputs.parent().remove();
elUpdateRows.find('td[colspan]').each(function() {
$(this).attr('colspan', (num_acc ? 1 + num_acc : 2))
});
elSelected.remove();
$('#trakt_accounts').change();
elTraktAuthResult.html('Deleted account: ' + JSONData.account_name);
}
});
}
},
'No' : {
'class' : 'red',
'action': function() {
e.preventDefault();
$('#trakt_accounts').change();
}
}
}
});
});
$('#testEmail').click(function () {
var status, host, port, tls, from, user, pwd, err, to;

View file

@ -191,8 +191,9 @@ FormToWizard.prototype = {
$section.data('elements', []);
//create each 'step' DIV and add it to main Steps Container:
var $stepwords = ['first', 'second', 'third'], $thestep = $('<div class="step disabledstep" />').data('section', i).html(($stepwords[i]
+ ' step') + '<div class="smalltext">' + $section.find('legend:eq(0)').text() + '<p></p></div>').appendTo($stepsguide);
var $stepwords = ['first', 'then', 'finally'],
$thestep = $('<div class="step disabledstep" />').data('section', i).html(($stepwords[i]) +
'<div class="smalltext">' + $section.find('legend:eq(0)').text() + '<p></p></div>').appendTo($stepsguide);
//assign behavior to each step div
$thestep.click(function(){

View file

@ -1 +1 @@
from trakt import TraktAPI
from trakt import TraktAPI

View file

@ -1,10 +1,10 @@
class traktException(Exception):
class TraktException(Exception):
pass
class traktAuthException(traktException):
class TraktAuthException(TraktException):
pass
class traktServerBusy(traktException):
class TraktServerBusy(TraktException):
pass

View file

@ -3,33 +3,144 @@ import certifi
import json
import sickbeard
import time
import datetime
from sickbeard import logger
from exceptions import traktException, traktAuthException # , traktServerBusy
from exceptions import TraktException, TraktAuthException # , TraktServerBusy
class TraktAccount:
max_auth_fail = 9
def __init__(self, account_id=None, token='', refresh_token='', auth_fail=0, last_fail=None, token_valid_date=None):
self.account_id = account_id
self._name = ''
self.token = token
self.refresh_token = refresh_token
self.auth_fail = auth_fail
self.last_fail = last_fail
self.token_valid_date = token_valid_date
@property
def name(self):
if self.token and self.active:
if not self._name:
try:
resp = TraktAPI().trakt_request('users/settings', send_oauth=self.account_id, sleep_retry=20)
self.reset_auth_failure()
if 'user' in resp:
self._name = resp['user']['username']
except TraktAuthException:
self.inc_auth_failure()
self._name = ''
except TraktException:
pass
else:
self._name = ''
return self._name
def reset_name(self):
self._name = ''
@property
def active(self):
return self.auth_fail < self.max_auth_fail and self.token
@property
def needs_refresh(self):
return not self.token_valid_date or self.token_valid_date - datetime.datetime.now() < datetime.timedelta(days=3)
@property
def token_expired(self):
return self.token_valid_date and datetime.datetime.now() > self.token_valid_date
def reset_auth_failure(self):
if self.auth_fail != 0:
self.auth_fail = 0
self.last_fail = None
def inc_auth_failure(self):
self.auth_fail += 1
self.last_fail = datetime.datetime.now()
def auth_failure(self):
if self.auth_fail < self.max_auth_fail:
if self.last_fail:
time_diff = datetime.datetime.now() - self.last_fail
if self.auth_fail % 3 == 0:
if time_diff > datetime.timedelta(days=1):
self.inc_auth_failure()
sickbeard.save_config()
elif time_diff > datetime.timedelta(minutes=15):
self.inc_auth_failure()
if self.auth_fail == self.max_auth_fail or time_diff > datetime.timedelta(hours=6):
sickbeard.save_config()
else:
self.inc_auth_failure()
class TraktAPI:
max_retrys = 3
def __init__(self, ssl_verify=True, timeout=None):
def __init__(self, timeout=None):
self.session = requests.Session()
self.verify = ssl_verify and sickbeard.TRAKT_VERIFY and certifi.where()
self.verify = sickbeard.TRAKT_VERIFY and certifi.where()
self.timeout = timeout or sickbeard.TRAKT_TIMEOUT
self.auth_url = sickbeard.TRAKT_BASE_URL
self.api_url = sickbeard.TRAKT_BASE_URL
self.headers = {
'Content-Type': 'application/json',
'trakt-api-version': '2',
'trakt-api-key': sickbeard.TRAKT_CLIENT_ID
}
self.headers = {'Content-Type': 'application/json',
'trakt-api-version': '2',
'trakt-api-key': sickbeard.TRAKT_CLIENT_ID}
def trakt_token(self, trakt_pin=None, refresh=False, count=0):
if 3 <= count:
sickbeard.TRAKT_ACCESS_TOKEN = ''
@staticmethod
def build_config_string(data):
return '!!!'.join('%s|%s|%s|%s|%s|%s' % (
value.account_id, value.token, value.refresh_token, value.auth_fail,
value.last_fail.strftime('%Y%m%d%H%M') if value.last_fail else '0',
value.token_valid_date.strftime('%Y%m%d%H%M%S') if value.token_valid_date else '0') for (key, value) in data.items())
@staticmethod
def read_config_string(data):
return dict((int(a.split('|')[0]), TraktAccount(
int(a.split('|')[0]), a.split('|')[1], a.split('|')[2], int(a.split('|')[3]),
datetime.datetime.strptime(a.split('|')[4], '%Y%m%d%H%M') if a.split('|')[4] != '0' else None,
datetime.datetime.strptime(a.split('|')[5], '%Y%m%d%H%M%S') if a.split('|')[5] != '0' else None)) for a in data.split('!!!') if data)
@staticmethod
def add_account(token, refresh_token, token_valid_date):
k = max(sickbeard.TRAKT_ACCOUNTS.keys() or [0]) + 1
sickbeard.TRAKT_ACCOUNTS[k] = TraktAccount(account_id=k, token=token, refresh_token=refresh_token, token_valid_date=token_valid_date)
sickbeard.save_config()
return k
@staticmethod
def replace_account(account, token, refresh_token, token_valid_date, refresh):
if account in sickbeard.TRAKT_ACCOUNTS:
sickbeard.TRAKT_ACCOUNTS[account].token = token
sickbeard.TRAKT_ACCOUNTS[account].refresh_token = refresh_token
sickbeard.TRAKT_ACCOUNTS[account].token_valid_date = token_valid_date
if not refresh:
sickbeard.TRAKT_ACCOUNTS[account].reset_name()
sickbeard.TRAKT_ACCOUNTS[account].reset_auth_failure()
sickbeard.save_config()
return True
else:
return False
elif 0 < count:
time.sleep(3)
@staticmethod
def delete_account(account):
if account in sickbeard.TRAKT_ACCOUNTS:
sickbeard.TRAKT_ACCOUNTS.pop(account)
sickbeard.save_config()
return True
return False
def trakt_token(self, trakt_pin=None, refresh=False, count=0, account=None):
if self.max_retrys <= count:
return False
0 < count and time.sleep(3)
data = {
'client_id': sickbeard.TRAKT_CLIENT_ID,
@ -38,62 +149,61 @@ class TraktAPI:
}
if refresh:
data['grant_type'] = 'refresh_token'
data['refresh_token'] = sickbeard.TRAKT_REFRESH_TOKEN
if account and account in sickbeard.TRAKT_ACCOUNTS:
data['grant_type'] = 'refresh_token'
data['refresh_token'] = sickbeard.TRAKT_ACCOUNTS[account].refresh_token
else:
return False
else:
data['grant_type'] = 'authorization_code'
if trakt_pin:
data['code'] = trakt_pin
headers = {
'Content-Type': 'application/json'
}
resp = self.trakt_request('oauth/token', data=data, headers=headers, url=self.auth_url, method='POST', count=count)
if 'access_token' in resp:
sickbeard.TRAKT_TOKEN = resp['access_token']
if 'refresh_token' in resp:
sickbeard.TRAKT_REFRESH_TOKEN = resp['refresh_token']
return True
return False
def validate_account(self):
resp = self.trakt_request('users/settings')
return 'account' in resp
def get_connected_user(self):
if sickbeard.TRAKT_TOKEN:
response = 'Connected to Trakt user account: %s'
if sickbeard.TRAKT_CONNECTED_ACCOUNT and sickbeard.TRAKT_TOKEN == sickbeard.TRAKT_CONNECTED_ACCOUNT[1] and sickbeard.TRAKT_CONNECTED_ACCOUNT[0]:
return response % sickbeard.TRAKT_CONNECTED_ACCOUNT[0]
resp = self.trakt_request('users/settings')
if 'user' in resp:
sickbeard.TRAKT_CONNECTED_ACCOUNT = [resp['user']['username'], sickbeard.TRAKT_TOKEN]
return response % sickbeard.TRAKT_CONNECTED_ACCOUNT[0]
return 'Not connected to Trakt'
def trakt_request(self, path, data=None, headers=None, url=None, method='GET', count=0):
if None is sickbeard.TRAKT_TOKEN:
logger.log(u'You must get a Trakt token. Check your Trakt settings', logger.WARNING)
return {}
headers = headers or self.headers
url = url or self.api_url
count += 1
headers['Authorization'] = 'Bearer ' + sickbeard.TRAKT_TOKEN
headers = {'Content-Type': 'application/json'}
try:
resp = self.session.request(method, url + path, headers=headers, timeout=self.timeout,
data=json.dumps(data) if data else [], verify=self.verify)
now = datetime.datetime.now()
resp = self.trakt_request('oauth/token', data=data, headers=headers, url=self.auth_url,
count=count, sleep_retry=0)
except (TraktAuthException, TraktException):
return False
if 'access_token' in resp and 'refresh_token' in resp and 'expires_in' in resp:
token_valid_date = now + datetime.timedelta(seconds=sickbeard.helpers.tryInt(resp['expires_in']))
if refresh or (not refresh and account and account in sickbeard.TRAKT_ACCOUNTS):
return self.replace_account(account, resp['access_token'], resp['refresh_token'], token_valid_date, refresh)
else:
return self.add_account(resp['access_token'], resp['refresh_token'], token_valid_date)
return False
def trakt_request(self, path, data=None, headers=None, url=None, count=0, sleep_retry=60, send_oauth=None, **kwargs):
count += 1
if count > self.max_retrys:
return {}
# wait before retry
count > 1 and time.sleep(sleep_retry)
headers = headers or self.headers
if send_oauth and send_oauth in sickbeard.TRAKT_ACCOUNTS:
if sickbeard.TRAKT_ACCOUNTS[send_oauth].active:
if sickbeard.TRAKT_ACCOUNTS[send_oauth].needs_refresh:
self.trakt_token(refresh=True, count=0, account=send_oauth)
if sickbeard.TRAKT_ACCOUNTS[send_oauth].token_expired:
return {}
headers['Authorization'] = 'Bearer %s' % sickbeard.TRAKT_ACCOUNTS[send_oauth].token
else:
return {}
kwargs = dict(headers=headers, timeout=self.timeout, verify=self.verify)
if data:
kwargs['data'] = json.dumps(data)
url = url or self.api_url
try:
resp = self.session.request(('GET', 'POST')['data' in kwargs.keys()],
url + path, **kwargs)
# check for http errors and raise if any are present
resp.raise_for_status()
@ -104,25 +214,37 @@ class TraktAPI:
code = getattr(e.response, 'status_code', None)
if not code:
if 'timed out' in e:
logger.log(u'Timeout connecting to Trakt. Try to increase timeout value in Trakt settings', logger.WARNING)
logger.log(u'Timeout connecting to Trakt', logger.WARNING)
# This is pretty much a fatal error if there is no status_code
# It means there basically was no response at all
else:
logger.log(u'Could not connect to Trakt. Error: {0}'.format(e), logger.WARNING)
elif 502 == code:
# Retry the request, Cloudflare had a proxying issue
logger.log(u'Retrying trakt api request: %s' % path, logger.WARNING)
return self.trakt_request(path, data, headers, url, method, count=count)
elif 401 == code:
if self.trakt_token(refresh=True, count=count):
sickbeard.save_config()
return self.trakt_request(path, data, headers, url, method, count=count)
logger.log(u'Retrying Trakt api request: %s' % path, logger.WARNING)
return self.trakt_request(path, data, headers, url, count=count, sleep_retry=sleep_retry, send_oauth=send_oauth)
elif 401 == code and path != 'oauth/token':
if send_oauth:
if sickbeard.TRAKT_ACCOUNTS[send_oauth].needs_refresh:
if self.trakt_token(refresh=True, count=count, account=send_oauth):
return self.trakt_request(path, data, headers, url, count=count, sleep_retry=sleep_retry, send_oauth=send_oauth)
else:
logger.log(u'Unauthorized. Please check your Trakt settings', logger.WARNING)
sickbeard.TRAKT_ACCOUNTS[send_oauth].auth_failure()
raise TraktAuthException()
else:
# sometimes the trakt server sends invalid token error even if it isn't
sickbeard.TRAKT_ACCOUNTS[send_oauth].auth_failure()
if count >= self.max_retrys:
raise TraktAuthException()
else:
return self.trakt_request(path, data, headers, url, count=count, sleep_retry=sleep_retry, send_oauth=send_oauth)
else:
logger.log(u'Unauthorized. Please check your Trakt settings', logger.WARNING)
raise traktAuthException()
raise TraktAuthException()
elif code in (500, 501, 503, 504, 520, 521, 522):
# http://docs.trakt.apiary.io/#introduction/status-codes
logger.log(u'Trakt may have some issues and it\'s unavailable. Try again later please', logger.WARNING)
logger.log(u'Trakt may have some issues and it\'s unavailable. Trying again', logger.WARNING)
self.trakt_request(path, data, headers, url, count=count, sleep_retry=sleep_retry, send_oauth=send_oauth)
elif 404 == code:
logger.log(u'Trakt error (404) the resource does not exist: %s' % url + path, logger.WARNING)
else:
@ -132,10 +254,12 @@ class TraktAPI:
# check and confirm Trakt call did not fail
if isinstance(resp, dict) and 'failure' == resp.get('status', None):
if 'message' in resp:
raise traktException(resp['message'])
raise TraktException(resp['message'])
if 'error' in resp:
raise traktException(resp['error'])
raise TraktException(resp['error'])
else:
raise traktException('Unknown Error')
raise TraktException('Unknown Error')
if send_oauth and send_oauth in sickbeard.TRAKT_ACCOUNTS:
sickbeard.TRAKT_ACCOUNTS[send_oauth].reset_auth_failure()
return resp

View file

@ -38,11 +38,14 @@ from sickbeard import helpers, logger, db, naming, metadata, providers, scene_ex
from sickbeard.config import CheckSection, check_setting_int, check_setting_str, ConfigMigrator, minimax
from sickbeard.common import SD, SKIPPED
from sickbeard.databases import mainDB, cache_db, failed_db
from indexers.indexer_config import INDEXER_TVDB
from indexers.indexer_api import indexerApi
from indexers.indexer_exceptions import indexer_shownotfound, indexer_exception, indexer_error, \
indexer_episodenotfound, indexer_attributenotfound, indexer_seasonnotfound, indexer_userabort, indexerExcepts
from sickbeard.providers.generic import GenericProvider
from lib.configobj import ConfigObj
from lib.libtrakt import TraktAPI
import trakt_helpers
PID = None
@ -50,7 +53,7 @@ CFG = None
CONFIG_FILE = None
# This is the version of the config we EXPECT to find
CONFIG_VERSION = 13
CONFIG_VERSION = 14
# Default encryption version (0 for None)
ENCRYPTION_VERSION = 0
@ -351,8 +354,6 @@ SYNOLOGYNOTIFIER_NOTIFY_ONDOWNLOAD = False
SYNOLOGYNOTIFIER_NOTIFY_ONSUBTITLEDOWNLOAD = False
USE_TRAKT = False
TRAKT_TOKEN = ''
TRAKT_REFRESH_TOKEN = ''
TRAKT_REMOVE_WATCHLIST = False
TRAKT_REMOVE_SERIESLIST = False
TRAKT_USE_WATCHLIST = False
@ -360,6 +361,7 @@ TRAKT_METHOD_ADD = 0
TRAKT_START_PAUSED = False
TRAKT_SYNC = False
TRAKT_DEFAULT_INDEXER = None
TRAKT_UPDATE_COLLECTION = {}
USE_PYTIVO = False
PYTIVO_NOTIFY_ONSNATCH = False
@ -449,6 +451,7 @@ TRAKT_STAGING = False
TRAKT_TIMEOUT = 60
TRAKT_VERIFY = True
TRAKT_CONNECTED_ACCOUNT = None
TRAKT_ACCOUNTS = {}
if TRAKT_STAGING:
# staging trakt values:
@ -484,7 +487,7 @@ def initialize(consoleLogging=True):
USE_XBMC, XBMC_ALWAYS_ON, XBMC_NOTIFY_ONSNATCH, XBMC_NOTIFY_ONDOWNLOAD, XBMC_NOTIFY_ONSUBTITLEDOWNLOAD, XBMC_UPDATE_FULL, XBMC_UPDATE_ONLYFIRST, \
XBMC_UPDATE_LIBRARY, XBMC_HOST, XBMC_USERNAME, XBMC_PASSWORD, BACKLOG_FREQUENCY, \
USE_KODI, KODI_ALWAYS_ON, KODI_NOTIFY_ONSNATCH, KODI_NOTIFY_ONDOWNLOAD, KODI_NOTIFY_ONSUBTITLEDOWNLOAD, KODI_UPDATE_FULL, KODI_UPDATE_ONLYFIRST, KODI_UPDATE_LIBRARY, KODI_HOST, KODI_USERNAME, KODI_PASSWORD, \
USE_TRAKT, TRAKT_CONNECTED_ACCOUNT, TRAKT_VERIFY, TRAKT_REMOVE_WATCHLIST, TRAKT_TOKEN, TRAKT_TIMEOUT, TRAKT_REFRESH_TOKEN, TRAKT_USE_WATCHLIST, TRAKT_METHOD_ADD, TRAKT_START_PAUSED, traktCheckerScheduler, TRAKT_SYNC, TRAKT_DEFAULT_INDEXER, TRAKT_REMOVE_SERIESLIST, \
USE_TRAKT, TRAKT_CONNECTED_ACCOUNT, TRAKT_ACCOUNTS, TRAKT_VERIFY, TRAKT_REMOVE_WATCHLIST, TRAKT_TIMEOUT, TRAKT_USE_WATCHLIST, TRAKT_METHOD_ADD, TRAKT_START_PAUSED, traktCheckerScheduler, TRAKT_SYNC, TRAKT_DEFAULT_INDEXER, TRAKT_REMOVE_SERIESLIST, TRAKT_UPDATE_COLLECTION, \
USE_PLEX, PLEX_NOTIFY_ONSNATCH, PLEX_NOTIFY_ONDOWNLOAD, PLEX_NOTIFY_ONSUBTITLEDOWNLOAD, PLEX_UPDATE_LIBRARY, \
PLEX_SERVER_HOST, PLEX_HOST, PLEX_USERNAME, PLEX_PASSWORD, DEFAULT_BACKLOG_FREQUENCY, MIN_BACKLOG_FREQUENCY, MAX_BACKLOG_FREQUENCY, BACKLOG_STARTUP, SKIP_REMOVED_FILES, \
showUpdateScheduler, __INITIALIZED__, LAUNCH_BROWSER, TRASH_REMOVE_SHOW, TRASH_ROTATE_LOGS, HOME_SEARCH_FOCUS, SORT_ARTICLE, showList, loadingShowList, UPDATE_SHOWS_ON_START, SHOW_UPDATE_HOUR, ALLOW_INCOMPLETE_SHOWDATA, \
@ -667,6 +670,8 @@ def initialize(consoleLogging=True):
NOTIFY_ON_UPDATE = bool(check_setting_int(CFG, 'General', 'notify_on_update', 1))
FLATTEN_FOLDERS_DEFAULT = bool(check_setting_int(CFG, 'General', 'flatten_folders_default', 0))
INDEXER_DEFAULT = check_setting_int(CFG, 'General', 'indexer_default', 0)
if INDEXER_DEFAULT and not indexerApi(INDEXER_DEFAULT).config['active']:
INDEXER_DEFAULT = INDEXER_TVDB
INDEXER_TIMEOUT = check_setting_int(CFG, 'General', 'indexer_timeout', 20)
ANIME_DEFAULT = bool(check_setting_int(CFG, 'General', 'anime_default', 0))
SCENE_DEFAULT = bool(check_setting_int(CFG, 'General', 'scene_default', 0))
@ -872,8 +877,6 @@ def initialize(consoleLogging=True):
check_setting_int(CFG, 'SynologyNotifier', 'synologynotifier_notify_onsubtitledownload', 0))
USE_TRAKT = bool(check_setting_int(CFG, 'Trakt', 'use_trakt', 0))
TRAKT_TOKEN = check_setting_str(CFG, 'Trakt', 'trakt_token', '')
TRAKT_REFRESH_TOKEN = check_setting_str(CFG, 'Trakt', 'trakt_refresh_token', '')
TRAKT_REMOVE_WATCHLIST = bool(check_setting_int(CFG, 'Trakt', 'trakt_remove_watchlist', 0))
TRAKT_REMOVE_SERIESLIST = bool(check_setting_int(CFG, 'Trakt', 'trakt_remove_serieslist', 0))
TRAKT_USE_WATCHLIST = bool(check_setting_int(CFG, 'Trakt', 'trakt_use_watchlist', 0))
@ -881,6 +884,8 @@ def initialize(consoleLogging=True):
TRAKT_START_PAUSED = bool(check_setting_int(CFG, 'Trakt', 'trakt_start_paused', 0))
TRAKT_SYNC = bool(check_setting_int(CFG, 'Trakt', 'trakt_sync', 0))
TRAKT_DEFAULT_INDEXER = check_setting_int(CFG, 'Trakt', 'trakt_default_indexer', 1)
TRAKT_UPDATE_COLLECTION = trakt_helpers.read_config_string(check_setting_str(CFG, 'Trakt', 'trakt_update_collection', ''))
TRAKT_ACCOUNTS = TraktAPI.read_config_string(check_setting_str(CFG, 'Trakt', 'trakt_accounts', ''))
CheckSection(CFG, 'pyTivo')
USE_PYTIVO = bool(check_setting_int(CFG, 'pyTivo', 'use_pytivo', 0))
@ -1219,8 +1224,8 @@ def start():
subtitlesFinderScheduler.start()
# start the trakt checker
if USE_TRAKT:
traktCheckerScheduler.start()
#if USE_TRAKT:
#traktCheckerScheduler.start()
started = True
@ -1295,13 +1300,13 @@ def halt():
except:
pass
if USE_TRAKT:
traktCheckerScheduler.stop.set()
logger.log(u'Waiting for the TRAKTCHECKER thread to exit')
try:
traktCheckerScheduler.join(10)
except:
pass
# if USE_TRAKT:
# traktCheckerScheduler.stop.set()
# logger.log(u'Waiting for the TRAKTCHECKER thread to exit')
# try:
# traktCheckerScheduler.join(10)
# except:
# pass
if DOWNLOAD_PROPERS:
properFinderScheduler.stop.set()
@ -1687,8 +1692,6 @@ def save_config():
new_config['Trakt'] = {}
new_config['Trakt']['use_trakt'] = int(USE_TRAKT)
new_config['Trakt']['trakt_token'] = TRAKT_TOKEN
new_config['Trakt']['trakt_refresh_token'] = TRAKT_REFRESH_TOKEN
new_config['Trakt']['trakt_remove_watchlist'] = int(TRAKT_REMOVE_WATCHLIST)
new_config['Trakt']['trakt_remove_serieslist'] = int(TRAKT_REMOVE_SERIESLIST)
new_config['Trakt']['trakt_use_watchlist'] = int(TRAKT_USE_WATCHLIST)
@ -1696,6 +1699,8 @@ def save_config():
new_config['Trakt']['trakt_start_paused'] = int(TRAKT_START_PAUSED)
new_config['Trakt']['trakt_sync'] = int(TRAKT_SYNC)
new_config['Trakt']['trakt_default_indexer'] = int(TRAKT_DEFAULT_INDEXER)
new_config['Trakt']['trakt_update_collection'] = trakt_helpers.build_config_string(TRAKT_UPDATE_COLLECTION)
new_config['Trakt']['trakt_accounts'] = TraktAPI.build_config_string(TRAKT_ACCOUNTS)
new_config['pyTivo'] = {}
new_config['pyTivo']['use_pytivo'] = int(USE_PYTIVO)

View file

@ -25,6 +25,7 @@ import sickbeard
import sickbeard.providers
from sickbeard import encodingKludge as ek
from sickbeard import helpers, logger, naming, db
from lib.libtrakt import TraktAPI
naming_ep_type = ('%(seasonnumber)dx%(episodenumber)02d',
@ -212,15 +213,15 @@ def change_USE_TRAKT(use_trakt):
return
sickbeard.USE_TRAKT = use_trakt
if sickbeard.USE_TRAKT:
sickbeard.traktCheckerScheduler.start()
else:
sickbeard.traktCheckerScheduler.stop.set()
logger.log(u'Waiting for the TRAKTCHECKER thread to exit')
try:
sickbeard.traktCheckerScheduler.join(10)
except:
pass
# if sickbeard.USE_TRAKT:
# sickbeard.traktCheckerScheduler.start()
# else:
# sickbeard.traktCheckerScheduler.stop.set()
# logger.log(u'Waiting for the TRAKTCHECKER thread to exit')
# try:
# sickbeard.traktCheckerScheduler.join(10)
# except:
# pass
def change_USE_SUBTITLES(use_subtitles):
@ -446,7 +447,8 @@ class ConfigMigrator():
10: 'Reset backlog frequency to default',
11: 'Migrate anime split view to new layout',
12: 'Add "hevc" and some non-english languages to ignore words if not found',
13: 'Change default dereferrer url to blank'}
13: 'Change default dereferrer url to blank',
14: 'Convert Trakt to multi-account'}
def migrate_config(self):
""" Calls each successive migration until the config is the same version as SG expects """
@ -601,7 +603,7 @@ class ConfigMigrator():
Reads in the old naming settings from your config and generates a new config template from them.
"""
# get the old settings from the file and store them in the new variable names
for prov in [curProvider for curProvider in providers.sortedProviderList() if curProvider.name == 'omgwtfnzbs']:
for prov in [curProvider for curProvider in sickbeard.providers.sortedProviderList() if curProvider.name == 'omgwtfnzbs']:
prov.username = check_setting_str(self.config_obj, 'omgwtfnzbs', 'omgwtfnzbs_uid', '')
prov.api_key = check_setting_str(self.config_obj, 'omgwtfnzbs', 'omgwtfnzbs_key', '')
@ -721,7 +723,7 @@ class ConfigMigrator():
if sickbeard.RECENTSEARCH_FREQUENCY < sickbeard.MIN_RECENTSEARCH_FREQUENCY:
sickbeard.RECENTSEARCH_FREQUENCY = sickbeard.MIN_RECENTSEARCH_FREQUENCY
for curProvider in providers.sortedProviderList():
for curProvider in sickbeard.providers.sortedProviderList():
if hasattr(curProvider, 'enable_recentsearch'):
curProvider.enable_recentsearch = bool(check_setting_int(
self.config_obj, curProvider.get_id().upper(), curProvider.get_id() + '_enable_dailysearch', 1))
@ -776,3 +778,9 @@ class ConfigMigrator():
# change dereferrer.org urls to blank, but leave any other url untouched
if sickbeard.ANON_REDIRECT == 'http://dereferer.org/?':
sickbeard.ANON_REDIRECT = ''
def _migrate_v14(self):
old_token = check_setting_str(self.config_obj, 'Trakt', 'trakt_token', '')
old_refresh_token = check_setting_str(self.config_obj, 'Trakt', 'trakt_refresh_token', '')
if old_token and old_refresh_token:
TraktAPI.add_account(old_token, old_refresh_token, None)

View file

@ -17,10 +17,63 @@
# along with SickGear. If not, see <http://www.gnu.org/licenses/>.
import os
import sickbeard
import time
from indexer_config import initConfig, indexerConfig
from sickbeard.helpers import proxy_setting
class ShowContainer(dict):
"""Simple dict that holds a series of Show instances
"""
def __init__(self):
self._stack = []
self._lastgc = time.time()
def __setitem__(self, key, value):
self._stack.append(key)
# keep only the 100th latest results
if time.time() - self._lastgc > 20:
for o in self._stack[:-100]:
del self[o]
self._stack = self._stack[-100:]
self._lastgc = time.time()
super(ShowContainer, self).__setitem__(key, value)
class DummyIndexer:
def __init__(self, *args, **kwargs):
self.config = {
'apikey': '',
'debug_enabled': False,
'custom_ui': None,
'proxy': None,
'cache_enabled': False,
'cache_location': '',
'valid_languages': [],
'langabbv_to_id': {},
'language': 'en',
'base_url': '',
}
self.corrections = {}
self.shows = ShowContainer()
def __getitem__(self, key):
return None
def __repr__(self):
return str(self.shows)
def search(self, series):
return []
class indexerApi(object):
def __init__(self, indexerID=None):
self.indexerID = int(indexerID) if indexerID else None
@ -30,7 +83,10 @@ class indexerApi(object):
def indexer(self, *args, **kwargs):
if self.indexerID:
return indexerConfig[self.indexerID]['module'](*args, **kwargs)
if indexerConfig[self.indexerID]['active']:
return indexerConfig[self.indexerID]['module'](*args, **kwargs)
else:
return DummyIndexer(*args, **kwargs)
@property
def config(self):

View file

@ -25,6 +25,7 @@ indexerConfig[INDEXER_TVDB] = {
'language': 'en',
'useZip': True,
},
'active': True,
}
indexerConfig[INDEXER_TVRAGE] = {
@ -34,6 +35,7 @@ indexerConfig[INDEXER_TVRAGE] = {
'api_params': {'apikey': 'Uhewg1Rr0o62fvZvUIZt',
'language': 'en',
},
'active': False,
}
# TVDB Indexer Settings

View file

@ -26,6 +26,7 @@ import nmjv2
import synoindex
import synologynotifier
import pytivo
import trakt
import growl
import prowl
@ -62,7 +63,7 @@ pushalot_notifier = pushalot.PushalotNotifier()
pushbullet_notifier = pushbullet.PushbulletNotifier()
# social
twitter_notifier = tweet.TwitterNotifier()
#trakt_notifier = trakt.TraktNotifier()
trakt_notifier = trakt.TraktNotifier()
email_notifier = emailnotify.EmailNotifier()
notifiers = [
@ -83,7 +84,7 @@ notifiers = [
pushalot_notifier,
pushbullet_notifier,
twitter_notifier,
# trakt_notifier,
trakt_notifier,
email_notifier,
]

View file

@ -18,10 +18,13 @@
import sickbeard
from sickbeard import logger
from lib.libtrakt import TraktAPI
from lib.libtrakt import TraktAPI, exceptions
class TraktNotifier:
def __init__(self):
pass
"""
A "notifier" for trakt.tv which keeps track of what has and hasn't been added to your library.
"""
@ -34,103 +37,71 @@ class TraktNotifier:
def notify_subtitle_download(self, ep_name, lang):
pass
def notify_git_update(self, new_version):
pass
def update_library(self, ep_obj):
@staticmethod
def update_collection(ep_obj):
"""
Sends a request to trakt indicating that the given episode is part of our library.
ep_obj: The TVEpisode object to add to trakt
Sends a request to trakt indicating that the given episode is part of our collection.
:param ep_obj: The TVEpisode object to add to trakt
"""
if sickbeard.USE_TRAKT:
if sickbeard.USE_TRAKT and sickbeard.TRAKT_ACCOUNTS:
# URL parameters
data = {
'tvdb_id': ep_obj.show.indexerid,
'title': ep_obj.show.name,
'year': ep_obj.show.startyear,
'episodes': [{
'season': ep_obj.season,
'episode': ep_obj.episode
}]
'shows': [
{
'title': ep_obj.show.name,
'year': ep_obj.show.startyear,
'ids': {},
}
]
}
if data is not None:
TraktCall('show/episode/library/%API%', self._api(), self._username(), self._password(), data)
if sickbeard.TRAKT_REMOVE_WATCHLIST:
TraktCall('show/episode/unwatchlist/%API%', self._api(), self._username(), self._password(), data)
indexer = ('tvrage', 'tvdb')[1 == ep_obj.show.indexer]
data['shows'][0]['ids'][indexer] = ep_obj.show.indexerid
if sickbeard.TRAKT_REMOVE_SERIESLIST:
data_show = None
# Add Season and Episode + Related Episodes
data['shows'][0]['seasons'] = [{'number': ep_obj.season, 'episodes': []}]
# URL parameters, should not need to recheck data (done above)
data = {
'shows': [
{
'tvdb_id': ep_obj.show.indexerid,
'title': ep_obj.show.name,
'year': ep_obj.show.startyear
}
]
}
TraktCall('show/unwatchlist/%API%', self._api(), self._username(), self._password(), data)
for relEp_Obj in [ep_obj] + ep_obj.relatedEps:
data['shows'][0]['seasons'][0]['episodes'].append({'number': relEp_Obj.episode})
# Remove all episodes from episode watchlist
# Start by getting all episodes in the watchlist
watchlist = TraktCall('user/watchlist/episodes.json/%API%/' + sickbeard.TRAKT_USERNAME, sickbeard.TRAKT_API, sickbeard.TRAKT_USERNAME, sickbeard.TRAKT_PASSWORD)
for tid, locations in sickbeard.TRAKT_UPDATE_COLLECTION.items():
if tid not in sickbeard.TRAKT_ACCOUNTS.keys():
continue
for loc in locations:
if not ep_obj.location.startswith('%s\\' % loc.rstrip('\\')):
continue
if watchlist is not None:
# Convert watchlist to only contain current show
for show in watchlist:
# Check if tvdb_id exists
if 'tvdb_id' in show:
if unicode(data['shows'][0]['tvdb_id']) == show['tvdb_id']:
data_show = {
'title': show['title'],
'tvdb_id': show['tvdb_id'],
'episodes': []
}
# Add series and episode (number) to the arry
for episodes in show['episodes']:
ep = {'season': episodes['season'], 'episode': episodes['number']}
data_show['episodes'].append(ep)
if data_show is not None:
TraktCall('show/episode/unwatchlist/%API%', sickbeard.TRAKT_API, sickbeard.TRAKT_USERNAME, sickbeard.TRAKT_PASSWORD, data_show)
warn, msg = False, ''
try:
resp = TraktAPI().trakt_request('sync/collection', data, send_oauth=tid)
if 'added' in resp and 'episodes' in resp['added'] and 0 < sickbeard.helpers.tryInt(resp['added']['episodes']):
msg = 'Added episode to'
elif 'updated' in resp and 'episodes' in resp['updated'] and 0 < sickbeard.helpers.tryInt(resp['updated']['episodes']):
msg = 'Updated episode in'
elif 'existing' in resp and 'episodes' in resp['existing'] and 0 < sickbeard.helpers.tryInt(resp['existing']['episodes']):
msg = 'Episode is already in'
elif 'not_found' in resp and 'episodes' in resp['not_found'] and 0 < sickbeard.helpers.tryInt(resp['not_found']['episodes']):
msg = 'Episode not found on Trakt, not adding to'
else:
warn, msg = True, 'Could not add episode to'
except exceptions.TraktAuthException, exceptions.TraktException:
warn, msg = True, 'Error adding episode to'
msg = 'Trakt: %s your %s collection' % (msg, sickbeard.TRAKT_ACCOUNTS[tid].name)
if not warn:
logger.log(msg)
else:
logger.log('Failed to get watchlist from trakt. Unable to remove episode from watchlist',
logger.ERROR)
logger.log(msg, logger.WARNING)
def test_notify(self, api, username, password):
"""
Sends a test notification to trakt with the given authentication info and returns a boolean
representing success.
api: The api string to use
username: The username to use
password: The password to use
Returns: True if the request succeeded, False otherwise
"""
data = TraktCall('account/test/%API%', api, username, password)
if data and data['status'] == 'success':
return True
def _username(self):
return sickbeard.TRAKT_USERNAME
def _password(self):
return sickbeard.TRAKT_PASSWORD
def _api(self):
return sickbeard.TRAKT_API
def _use_me(self):
@staticmethod
def _use_me():
return sickbeard.USE_TRAKT

View file

@ -1034,7 +1034,7 @@ class PostProcessor(object):
notifiers.pytivo_notifier.update_library(ep_obj)
# do the library update for Trakt
# notifiers.trakt_notifier.update_library(ep_obj)
notifiers.trakt_notifier.update_collection(ep_obj)
self._run_extra_scripts(ep_obj)

View file

@ -556,7 +556,7 @@ class GenericProvider:
value *= 1024 ** ['b', 'k', 'm', 'g', 't'].index(re.findall('(t|g|m|k)[i]?b', size_dim.lower())[0])
except IndexError:
pass
return int(math.ceil(value))
return long(math.ceil(value))
class NZBProvider(object, GenericProvider):

View file

@ -459,13 +459,13 @@ class QueueItemAdd(ShowQueueItem):
self.show.flushEpisodes()
if sickbeard.USE_TRAKT:
# if there are specific episodes that need to be added by trakt
sickbeard.traktCheckerScheduler.action.manageNewShow(self.show)
# add show to trakt.tv library
if sickbeard.TRAKT_SYNC:
sickbeard.traktCheckerScheduler.action.addShowToTraktLibrary(self.show)
# if sickbeard.USE_TRAKT:
# # if there are specific episodes that need to be added by trakt
# sickbeard.traktCheckerScheduler.action.manageNewShow(self.show)
#
# # add show to trakt.tv library
# if sickbeard.TRAKT_SYNC:
# sickbeard.traktCheckerScheduler.action.addShowToTraktLibrary(self.show)
# Load XEM data to DB for show
sickbeard.scene_numbering.xem_refresh(self.show.indexerid, self.show.indexer, force=True)
@ -578,6 +578,11 @@ class QueueItemUpdate(ShowQueueItem):
ShowQueueItem.run(self)
if not sickbeard.indexerApi(self.show.indexer).config['active']:
logger.log(u'Indexer %s is marked inactive, aborting update for show %s and continue with refresh.' % (sickbeard.indexerApi(self.show.indexer).config['name'], self.show.name))
sickbeard.showQueueScheduler.action.refreshShow(self.show, self.force, self.scheduled_update, after_update=True)
return
logger.log(u'Beginning update of ' + self.show.name)
logger.log(u'Retrieving show info from ' + sickbeard.indexerApi(self.show.indexer).name + '', logger.DEBUG)

View file

@ -0,0 +1,57 @@
import ast
import base64
import re
import sickbeard
from helpers import tryInt
def read_config_string(data):
return data and dict((tryInt(x[0]), x[1]) for x in ast.literal_eval(data)) or {}
def build_config(**kwargs):
"""
kwargs is filtered for settings that enable updates to Trakt
:param kwargs: kwargs to be filtered for settings that enable updates to Trakt
:return: dict of parsed config kwargs where k is Trakt account id, v is a parent location
"""
config = {}
root_dirs = []
if sickbeard.ROOT_DIRS:
root_pieces = sickbeard.ROOT_DIRS.split('|')
root_dirs = root_pieces[1:]
for item in [re.findall('update_trakt_(\d+)_(.*)', k) for k, v in kwargs.items() if k.startswith('update_trakt_')]:
for account_id, location in item:
account_id = tryInt(account_id, None)
if None is account_id:
continue
for cur_dir in root_dirs:
account_id = tryInt(account_id, None)
if account_id and base64.urlsafe_b64encode(cur_dir) == location:
if isinstance(config.get(account_id), list):
config[account_id] += [cur_dir]
else:
config[account_id] = [cur_dir]
return config
def build_config_string(config):
"""
:param config: dicts of Trakt account id, parent location
:return: string csv of parsed config kwargs for config file
"""
return unicode(config.items())
def trakt_collection_remove_account(account_id):
if account_id in sickbeard.TRAKT_UPDATE_COLLECTION:
sickbeard.TRAKT_UPDATE_COLLECTION.pop(account_id)
sickbeard.save_config()
return True
return False

View file

@ -1312,17 +1312,17 @@ class TVShow(object):
def getOverview(self, epStatus):
if ARCHIVED == epStatus:
status, quality = Quality.splitCompositeStatus(epStatus)
if ARCHIVED == status:
return Overview.GOOD
if WANTED == epStatus:
if WANTED == status:
return Overview.WANTED
if epStatus in (SKIPPED, IGNORED):
if status in (SKIPPED, IGNORED):
return Overview.SKIPPED
if epStatus in (UNAIRED, UNKNOWN):
if status in (UNAIRED, UNKNOWN):
return Overview.UNAIRED
if epStatus in Quality.DOWNLOADED + Quality.SNATCHED + Quality.SNATCHED_PROPER + Quality.FAILED + Quality.SNATCHED_BEST:
if status in Quality.DOWNLOADED + Quality.SNATCHED + Quality.SNATCHED_PROPER + Quality.FAILED + Quality.SNATCHED_BEST:
status, quality = Quality.splitCompositeStatus(epStatus)
if FAILED == status:
return Overview.WANTED
if status in (SNATCHED, SNATCHED_PROPER, SNATCHED_BEST):

View file

@ -18,15 +18,16 @@
from __future__ import with_statement
import os
import time
import urllib
import re
import base64
import datetime
import dateutil.parser
import random
import traceback
import itertools
import os
import random
import re
import time
import traceback
import urllib
from mimetypes import MimeTypes
from Cheetah.Template import Template
@ -57,7 +58,8 @@ from lib import subliminal
from lib.dateutil import tz
from lib.unrar2 import RarFile
from lib.libtrakt import TraktAPI
from lib.libtrakt.exceptions import traktException, traktAuthException
from lib.libtrakt.exceptions import TraktException, TraktAuthException
from trakt_helpers import build_config, trakt_collection_remove_account
from sickbeard.bs4_parser import BS4Parser
@ -883,24 +885,54 @@ class Home(MainHandler):
return '{"message": "Unable to find NMJ Database at location: %(dbloc)s. Is the right location selected and PCH running?", "database": ""}' % {
"dbloc": dbloc}
def trakt_authenticate(self, pin=None):
def trakt_authenticate(self, pin=None, account=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
if None is pin:
return 'Trakt PIN required for authentication'
return json.dumps({'result': 'Fail', 'error_message': 'Trakt PIN required for authentication'})
if account and 'new' == account:
account = None
acc = None
if account:
acc = sickbeard.helpers.tryInt(account, -1)
if 0 < acc and acc not in sickbeard.TRAKT_ACCOUNTS:
return json.dumps({'result': 'Fail', 'error_message': 'Fail: cannot update non-existing account'})
json_fail_auth = json.dumps({'result': 'Fail', 'error_message': 'Trakt NOT authenticated'})
try:
TraktAPI().trakt_token(pin)
except traktAuthException:
return 'Fail: Trakt NOT authenticated'
resp = TraktAPI().trakt_token(pin, account=acc)
except TraktAuthException:
return json_fail_auth
if not account and isinstance(resp, bool) and not resp:
return json_fail_auth
sickbeard.USE_TRAKT = True
sickbeard.save_config()
return '%s %s' % ('Success: Trakt authenticated.', self.trakt_get_connected_account())
if not sickbeard.USE_TRAKT:
sickbeard.USE_TRAKT = True
sickbeard.save_config()
pick = resp if not account else acc
return json.dumps({'result': 'Success',
'account_id': sickbeard.TRAKT_ACCOUNTS[pick].account_id,
'account_name': sickbeard.TRAKT_ACCOUNTS[pick].name})
@staticmethod
def trakt_get_connected_account():
return TraktAPI().get_connected_user()
def trakt_delete(self, accountid=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
if accountid:
aid = sickbeard.helpers.tryInt(accountid, None)
if None is not aid:
if aid in sickbeard.TRAKT_ACCOUNTS:
account = {'result': 'Success',
'account_id': sickbeard.TRAKT_ACCOUNTS[aid].account_id,
'account_name': sickbeard.TRAKT_ACCOUNTS[aid].name}
if TraktAPI.delete_account(aid):
trakt_collection_remove_account(aid)
account['num_accounts'] = len(sickbeard.TRAKT_ACCOUNTS)
return json.dumps(account)
return json.dumps({'result': 'Not found: Account to delete'})
return json.dumps({'result': 'Not found: Invalid account id'})
def loadShowNotifyLists(self, *args, **kwargs):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
@ -968,6 +1000,46 @@ class Home(MainHandler):
return notifiers.pushbullet_notifier.get_devices(accessToken)
def viewchanges(self):
t = PageTemplate(headers=self.request.headers, file='viewchanges.tmpl')
t.changelist = [{'type': 'rel', 'ver': '', 'date': 'Nothing to display at this time'}]
url = 'https://raw.githubusercontent.com/wiki/SickGear/SickGear/sickgear/CHANGES.md'
response = helpers.getURL(url)
if not response:
return t.respond()
data = response.replace('\xef\xbb\xbf', '').splitlines()
output, change, max_rel = [], {}, 5
for line in data:
if not line.strip():
continue
if line.startswith(' '):
change_parts = re.findall('^[\W]+(.*)$', line)
change['text'] += change_parts and (' %s' % change_parts[0].strip()) or ''
else:
if change:
output.append(change)
if line.startswith('* '):
change_parts = re.findall(r'^[\*\W]+(Add|Change|Fix|Port|Remove|Update)\W(.*)', line)
change = change_parts and {'type': change_parts[0][0], 'text': change_parts[0][1].strip()} or {}
elif line.startswith('### '):
rel_data = re.findall(r'(?im)^###\W*([^\s]+)\W\(([^\)]+)\)', line)
rel_data and output.append({'type': 'rel', 'ver': rel_data[0][0], 'date': rel_data[0][1]})
max_rel -= 1
elif line.startswith('# '):
max_data = re.findall(r'^#\W*([\d]+)\W*$', line)
max_rel = max_data and helpers.tryInt(max_data[0], None) or 5
if not max_rel:
break
if change:
output.append(change)
t.changelist = output
return t.respond()
def shutdown(self, pid=None):
if str(pid) != str(sickbeard.PID):
@ -1436,9 +1508,9 @@ class Home(MainHandler):
showObj) or sickbeard.showQueueScheduler.action.isBeingUpdated(showObj): # @UndefinedVariable
return self._genericMessage("Error", "Shows can't be deleted while they're being added or updated.")
if sickbeard.USE_TRAKT and sickbeard.TRAKT_SYNC:
# remove show from trakt.tv library
sickbeard.traktCheckerScheduler.action.removeShowFromTraktLibrary(showObj)
# if sickbeard.USE_TRAKT and sickbeard.TRAKT_SYNC:
# # remove show from trakt.tv library
# sickbeard.traktCheckerScheduler.action.removeShowFromTraktLibrary(showObj)
showObj.deleteShow(bool(full))
@ -2117,10 +2189,10 @@ class NewHomeAddShows(Home):
filtered = []
try:
resp = TraktAPI(ssl_verify=sickbeard.TRAKT_VERIFY, timeout=sickbeard.TRAKT_TIMEOUT).trakt_request(url)
resp = TraktAPI().trakt_request(url)
if len(resp):
filtered = resp
except traktException as e:
except TraktException as e:
logger.log(u'Could not connect to Trakt service: %s' % ex(e), logger.WARNING)
return filtered
@ -2276,7 +2348,8 @@ class NewHomeAddShows(Home):
t.provided_show_dir = show_dir
t.other_shows = other_shows
t.provided_indexer = int(indexer or sickbeard.INDEXER_DEFAULT)
t.indexers = sickbeard.indexerApi().indexers
t.indexers = dict([(i, sickbeard.indexerApi().indexers[i]) for i in sickbeard.indexerApi().indexers
if sickbeard.indexerApi(i).config['active']])
t.whitelist = []
t.blacklist = []
t.groups = []
@ -2471,10 +2544,31 @@ class NewHomeAddShows(Home):
return self.browse_trakt('shows/trending?limit=%s&' % 100, 'Trending at Trakt', mode='trending')
def traktPopular(self, *args, **kwargs):
return self.browse_trakt('shows/popular?limit=%s&' % 100, 'Popular at Trakt', mode='popular')
def traktWatched(self, *args, **kwargs):
return self.browse_trakt('shows/watched/monthly?limit=%s&' % 100, 'Most watched at Trakt during the last month', mode='watched')
def traktCollected(self, *args, **kwargs):
return self.browse_trakt('shows/collected/monthly?limit=%s&' % 100, 'Most collected at Trakt during the last month', mode='collected')
def traktAnticipated(self, *args, **kwargs):
return self.browse_trakt('shows/anticipated?limit=%s&' % 100, 'Anticipated at Trakt', mode='anticipated')
def traktRecommended(self, *args, **kwargs):
account = sickbeard.helpers.tryInt(kwargs.get('account'), None)
try:
name = sickbeard.TRAKT_ACCOUNTS[account].name
except KeyError:
return self.traktDefault()
return self.browse_trakt('recommendations/shows?limit=%s&' % 100,
'Recommended for <b class="grey-text">you</b> by Trakt', mode='recommended')
'Recommended for <b class="grey-text">%s</b> by Trakt' % name, mode='recommended-%s' % account, send_oauth=account)
def traktNewShows(self, *args, **kwargs):
@ -2488,18 +2582,25 @@ class NewHomeAddShows(Home):
dt=datetime.datetime.now() + datetime.timedelta(days=-16), d_preset='%Y-%m-%d'), 32), 'Season premieres at Trakt',
mode='newseasons', footnote='Note; Expect default placeholder images in this list')
def browse_trakt(self, url, browse_title, *args, **kwargs):
def traktDefault(self):
return self.redirect('/home/addShows/traktTrending/')
def browse_trakt(self, url_path, browse_title, *args, **kwargs):
browse_type = 'Trakt'
normalised, filtered = ([], [])
if 'recommended' == kwargs.get('mode', None) and not sickbeard.USE_TRAKT:
if not sickbeard.USE_TRAKT and 'recommended' in kwargs.get('mode', ''):
error_msg = 'To browse personal recommendations, enable Trakt.tv in Config/Notifications/Social'
return self.browse_shows(browse_type, browse_title, filtered, error_msg=error_msg, show_header=1, **kwargs)
error_msg = None
try:
resp = TraktAPI(ssl_verify=sickbeard.TRAKT_VERIFY, timeout=sickbeard.TRAKT_TIMEOUT).trakt_request('%sextended=full,images' % url)
account = kwargs.get('send_oauth', None)
if account:
account = sickbeard.helpers.tryInt(account)
resp = TraktAPI().trakt_request('%sextended=full,images' % url_path, send_oauth=account)
if resp:
if 'show' in resp[0]:
if 'first_aired' in resp[0]:
@ -2511,10 +2612,10 @@ class NewHomeAddShows(Home):
for item in resp:
normalised.append({u'show': item})
del resp
except traktAuthException as e:
except TraktAuthException as e:
logger.log(u'Pin authorisation needed to connect to Trakt service: %s' % ex(e), logger.WARNING)
error_msg = 'Unauthorized: Get another pin in the Notifications Trakt settings'
except traktException as e:
except TraktException as e:
logger.log(u'Could not connect to Trakt service: %s' % ex(e), logger.WARNING)
except (IndexError, KeyError):
pass
@ -3704,6 +3805,8 @@ class ConfigGeneral(Config):
t = PageTemplate(headers=self.request.headers, file='config_general.tmpl')
t.submenu = self.ConfigMenu
t.show_tags = ', '.join(sickbeard.SHOW_TAGS)
t.indexers = dict([(i, sickbeard.indexerApi().indexers[i]) for i in sickbeard.indexerApi().indexers
if sickbeard.indexerApi(i).config['active']])
return t.respond()
def saveRootDirs(self, rootDirString=None):
@ -3836,6 +3939,8 @@ class ConfigGeneral(Config):
if indexer_default:
sickbeard.INDEXER_DEFAULT = config.to_int(indexer_default)
if not sickbeard.indexerApi(sickbeard.INDEXER_DEFAULT).config['active']:
sickbeard.INDEXER_DEFAULT = INDEXER_TVDB
if indexer_timeout:
sickbeard.INDEXER_TIMEOUT = config.to_int(indexer_timeout)
@ -4650,9 +4755,18 @@ class ConfigProviders(Config):
class ConfigNotifications(Config):
def index(self, *args, **kwargs):
t = PageTemplate(headers=self.request.headers, file='config_notifications.tmpl')
t.submenu = self.ConfigMenu
t.root_dirs = []
if sickbeard.ROOT_DIRS:
root_pieces = sickbeard.ROOT_DIRS.split('|')
root_default = helpers.tryInt(root_pieces[0], None)
for i, location in enumerate(root_pieces[1:]):
t.root_dirs.append({'root_def': root_default and i == root_default,
'loc': location,
'b64': base64.urlsafe_b64encode(location)})
return t.respond()
def saveNotifications(self, use_xbmc=None, xbmc_always_on=None, xbmc_notify_onsnatch=None,
@ -4684,7 +4798,7 @@ class ConfigNotifications(Config):
use_trakt=None, trakt_pin=None,
trakt_remove_watchlist=None, trakt_use_watchlist=None, trakt_method_add=None,
trakt_start_paused=None, trakt_sync=None,
trakt_default_indexer=None, trakt_remove_serieslist=None,
trakt_default_indexer=None, trakt_remove_serieslist=None, trakt_collection=None, trakt_accounts=None,
use_synologynotifier=None, synologynotifier_notify_onsnatch=None,
synologynotifier_notify_ondownload=None, synologynotifier_notify_onsubtitledownload=None,
use_pytivo=None, pytivo_notify_onsnatch=None, pytivo_notify_ondownload=None,
@ -4700,7 +4814,7 @@ class ConfigNotifications(Config):
use_email=None, email_notify_onsnatch=None, email_notify_ondownload=None,
email_notify_onsubtitledownload=None, email_host=None, email_port=25, email_from=None,
email_tls=None, email_user=None, email_password=None, email_list=None, email_show_list=None,
email_show=None):
email_show=None, **kwargs):
results = []
@ -4810,7 +4924,8 @@ class ConfigNotifications(Config):
synologynotifier_notify_onsubtitledownload)
sickbeard.USE_TRAKT = config.checkbox_to_value(use_trakt)
sickbeard.traktCheckerScheduler.silent = not sickbeard.USE_TRAKT
# sickbeard.traktCheckerScheduler.silent = not sickbeard.USE_TRAKT
sickbeard.TRAKT_UPDATE_COLLECTION = build_config(**kwargs)
# sickbeard.TRAKT_DEFAULT_INDEXER = int(trakt_default_indexer)
# sickbeard.TRAKT_SYNC = config.checkbox_to_value(trakt_sync)
# sickbeard.TRAKT_USE_WATCHLIST = config.checkbox_to_value(trakt_use_watchlist)