Browse Source

Merge branch 'feature/AddTVCCards' into develop

pull/1289/head
JackDandy 5 years ago
parent
commit
8b7866bb76
  1. 1
      CHANGES.md
  2. BIN
      gui/slick/css/fonts/sgicons.eot
  3. 1
      gui/slick/css/fonts/sgicons.svg
  4. BIN
      gui/slick/css/fonts/sgicons.ttf
  5. BIN
      gui/slick/css/fonts/sgicons.woff
  6. 23
      gui/slick/css/style.css
  7. 36
      gui/slick/interfaces/default/home_browseShows.tmpl
  8. 9
      gui/slick/interfaces/default/inc_top.tmpl
  9. 7
      sickbeard/__init__.py
  10. 220
      sickbeard/webserve.py

1
CHANGES.md

@ -1,6 +1,7 @@
### 0.22.0 (2020-xx-xx xx:xx:xx UTC)
* Add menu Shows/"Metacritic Cards"
* Add menu Shows/"TV Calendar Cards"
* Change make web UI calls async so that, for example, process media will not block page requests
* Change improve speed of backlog overview
* Fix the missing snatched low quality on backlog overview

BIN
gui/slick/css/fonts/sgicons.eot

Binary file not shown.

1
gui/slick/css/fonts/sgicons.svg

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 216 KiB

After

Width:  |  Height:  |  Size: 224 KiB

BIN
gui/slick/css/fonts/sgicons.ttf

Binary file not shown.

BIN
gui/slick/css/fonts/sgicons.woff

Binary file not shown.

23
gui/slick/css/style.css

@ -678,6 +678,10 @@ inc_top.tmpl
content:"\e890"
}
.sgicon-tvc:before{
content:"\e891"
}
.sgicon-imdb:before{
content:"\e898"
}
@ -1415,6 +1419,25 @@ home_browseShows.tmpl
background-image:url("../images/poster-dark.jpg")
}
#browse-list.tvcalendar .browse-image{
height:104px /* 62% of image height */
}
#browse-list.tvcalendar .show-toggle-hide{
top:105px
}
#browse-list.tvcalendar .show-card{
height:172px /* -169px */
}
#browse-list.tvcalendar.no-votes .show-card{
height:155px
}
#browse-list.tvcalendar .show-card-inner .heart.icon-glyph{
margin-left:0
}
/* =======================================================================
home_postprocess.tmpl
========================================================================== */

36
gui/slick/interfaces/default/home_browseShows.tmpl

@ -7,11 +7,15 @@
<% def sg_var(varname, default=False): return getattr(sickbeard, varname, default) %>#slurp#
<% def sg_str(varname, default=''): return getattr(sickbeard, varname, default) %>#slurp#
##
#set $mode = $kwargs and $kwargs.get('mode', '')
#set $use_votes = $kwargs and $kwargs.get('use_votes', True)
#set $use_ratings = $kwargs and $kwargs.get('use_ratings', True)
##
#set global $title='Browse %s Shows' % $browse_type
#set global $header='Browse Shows'
#set global $sbPath='..'
#set global $topmenu='home'
#set global $page_body_attr = 'browse-list'
#set global $page_body_attr = 'browse-list" class="%s%s' % ($browse_type.lower(), ('', ' no-votes')[not $use_votes])
#set sg_root = $getVar('sbRoot', WEB_ROOT)
##
#import os.path
@ -174,7 +178,7 @@ $(document).ready(function(){
$('#showfilter').on('change', function(){
var filterValue = this.value;
if (-1 == filterValue.indexOf('trakt') && -1 == filterValue.indexOf('imdb') && -1 == filterValue.indexOf('mc_') && -1 == filterValue.indexOf('default')) {
if (-1 == filterValue.indexOf('trakt') && -1 == filterValue.indexOf('imdb') && -1 == filterValue.indexOf('mc_') && -1 == filterValue.indexOf('tvc_') && -1 == filterValue.indexOf('default')) {
var el$ = $('#container')
el$.on('layoutComplete', llUpdate);
el$.isotope({ filter: filterValue });
@ -208,8 +212,6 @@ $(document).ready(function(){
#end if
<h1 style="margin-bottom:0" class="grey-text #echo '%s">%s' % $heading#</h1>
#set $mode = $kwargs and $kwargs.get('mode', '')
#set $use_votes = $kwargs and $kwargs.get('use_votes')
#if $all_shows or ($kwargs and $kwargs.get('show_header'))
<div class="pull-right" style="margin-top:-35px">
<select id="showsort" class="form-control form-control-inline input-sm">
@ -234,8 +236,10 @@ $(document).ready(function(){
#if $use_votes
<option value="by_votes">Votes</option>
#end if
#if $use_ratings
<option value="by_rating">% Rating</option>
#if $use_votes
#end if
#if $use_ratings and $use_votes
<option value="by_rating_votes">% Rating > Votes</option>
#end if
</optgroup>
@ -322,6 +326,13 @@ $(document).ready(function(){
<option value="mc_newseries?more=1"#echo ('', selected + ' disabled')[mode.endswith('more')]#>... list more</option>
#end if
</optgroup>
#elif 'TVCalendar' == $browse_type
<optgroup label="TVCalendar">
#for $page in $kwargs.get('pages') or []
<option value="$page[0]"#echo ('', selected)[$mode in $page[0] and ($kwargs and $kwargs.get('page', 'n/a') in $page[0] or $kwargs and not $kwargs.get('page') and 'this' in $page[1])]#>$page[1]</option>
#end for
<option value="tvc_latest"#echo ('', selected)['latest' == $mode]#>Latest additions</option>
</optgroup>
#end if
</select>
#end if
@ -352,15 +363,18 @@ $(document).ready(function(){
#set $show_id = $this_show.get('show_id')
#set $known = ('not', '')[bool($this_show.get('indb'))]
#set $hide = ('', 'hide ')[bool($this_show.get('hide'))]
#set $data_rating = re.search(r'^\d+$', '%s' % $this_show['rating']) and $this_show['rating'] or 0
<div class="show-card ${hide}${known}inlibrary" data-name="#echo re.sub(r'([\'\"])', r'', $this_show['title'])#" data_id="$show_id" data-rating="$this_show['rating']" data-votes="$this_show['votes']" data-premiered="$this_show['premiered']">
<div class="show-card ${hide}${known}inlibrary" data-name="#echo re.sub(r'([\'\"])', r'', $this_show['title'])#" data_id="$show_id" data-rating="$data_rating" data-votes="$this_show['votes']" data-premiered="$this_show['premiered']">
<div class="show-card-inner">
<div class="browse-image">
<a class="browse-image" href="<%= anon_url(this_show['url_src_db']) %>" target="_blank"
title="<span style='color: rgb(66, 139, 202)'>$re.sub(r'(?m)\s+\((?:19|20)\d\d\)\s*$', '', $title_html)</span>#if $this_show['genres']#<br /><div style='font-weight:bold'>(<em>$this_show['genres']</em>)</div>#end if#
<p style='margin:0 0 2px'>#echo re.sub(r'([,\.!][^,\.!]*?)$', '...', re.sub(r'([!\?\.])(?=\w)', r'\1 ', $overview))#</p>
<p style='margin:0 0 2px'>#echo re.sub(r'([,\.!][^,\.!]*?)$', '...', re.sub(r'([!\?\.])(?=\w)', r'\1 ', $overview)).replace('.....', '...')#</p>
<p><span style='font-weight:bold;font-size:0.9em;color:#888'><em>#if $kwargs and 'newseasons' == $mode#Air#else#First air#end if##echo ('s', 'ed')[$this_show['when_past']]#: $this_show['premiered_str']</em></span>
#if $this_show.get('ended_str')# - <span style='font-weight:bold;font-size:0.9em;color:#888'><em>Ended: $this_show['ended_str']</em></span>#end if#</p>
#if $this_show.get('ended_str')# - <span style='font-weight:bold;font-size:0.9em;color:#888'><em>Ended: $this_show['ended_str']</em></span>#end if#
#if $this_show.get('network')#<br><span style='font-weight:bold;font-size:0.9em;color:#888'><em>On: $this_show['network']</em></span>#end if#
</p>
<span style='float:right'>Click for more at <span class='boldest'>$browse_type</span></span>">
#if 'poster' in $this_show['images']
#set $image = $this_show['images']['poster']['thumb']
@ -379,7 +393,9 @@ $(document).ready(function(){
<a class="show-toggle-hide" href="$sg_root/add-shows/show-toggle-hide?ids=$show_id" title="#echo ('H', 'Unh')[any($hide)]#ide"><i class="sgicon-delete"></i></a>
#end if
<div class="clearfix">
<p>$this_show['rating']%<i class="heart icon-glyph"></i>#if $use_votes#<i>$this_show['votes'] votes</i>#end if#</p>
#if $use_ratings or $use_votes
<p>#if $use_ratings#$this_show['rating']%#end if##if $use_votes#<i class="heart icon-glyph"></i><i>$this_show['votes'] votes</i>#end if#</p>#slurp#
#end if
#if 'url_tvdb' in $this_show and $this_show['url_tvdb']
<a class="service" href="<%= anon_url(this_show['url_tvdb']) %>" onclick="window.open(this.href, '_blank'); return false;"
title="View <span class='boldest'>tvdb</span> detail for <span style='color: rgb(66, 139, 202)'>$title_html</span>">
@ -413,7 +429,7 @@ $(document).ready(function(){
#if $kwargs and $kwargs.get('error_msg')
$kwargs['error_msg']
#else
$browse_type API did not return results, this can happen from time to time.
$browse_type did not return results, this can happen from time to time.
<br /><br />This view should auto refresh every 10 mins.
#end if
</p>

9
gui/slick/interfaces/default/inc_top.tmpl

@ -173,6 +173,13 @@
#end if
<li><a href="$sbRoot/add-shows/mc-default/" tabindex="$tab#set $tab += 1#"><i class="sgicon-metac"></i>Metacritic Cards
<div class="menu-item-desc opacity60">$mc_mode...</div></a></li>
#set $tvc_mode = 'new shows'
#set $tvc_modes = dict(tvc_newshows='new shows', tvc_returning='returning', tvc_latest='latest')
#if $sg_var('TVC_MRU') in $tvc_modes
#set $tvc_mode = $tvc_modes[$sg_var('TVC_MRU')]
#end if
<li><a href="$sbRoot/add-shows/tvc-default/" tabindex="$tab#set $tab += 1#"><i class="sgicon-tvc"></i>TV Calendar Cards
<div class="menu-item-desc opacity60">$tvc_mode...</div></a></li>
#if $sg_var('USE_ANIDB')
<li><a href="$sbRoot/add-shows/anime-default/" tabindex="$tab#set $tab += 1#"><div class="img-anime-16 square-16"></div>Anime Cards
<div class="menu-item-desc opacity60">browse anime to add</div></a></li>
@ -182,7 +189,7 @@
#end if
</ul>
<ul class="nav added-last">
#set $added_last = ($sg_var('showList', []) or [])[-7:][::-1]
#set $added_last = ($sg_var('showList', []) or [])[-9:][::-1]
#if not $added_last:
<li><span style="padding-left:20px">This will list added shows</span></li>
#else

7
sickbeard/__init__.py

@ -584,6 +584,7 @@ else:
TRAKT_BASE_URL = 'https://api.trakt.tv/'
MC_MRU = ''
TVC_MRU = ''
COOKIE_SECRET = b64encodestring(uuid.uuid4().bytes + uuid.uuid4().bytes)
@ -721,7 +722,7 @@ def init_stage_1(console_logging):
global USE_TRAKT, TRAKT_CONNECTED_ACCOUNT, TRAKT_ACCOUNTS, TRAKT_MRU, TRAKT_VERIFY, \
TRAKT_USE_WATCHLIST, TRAKT_REMOVE_WATCHLIST, TRAKT_TIMEOUT, TRAKT_METHOD_ADD, TRAKT_START_PAUSED, \
TRAKT_SYNC, TRAKT_DEFAULT_INDEXER, TRAKT_REMOVE_SERIESLIST, TRAKT_UPDATE_COLLECTION, \
MC_MRU, \
MC_MRU, TVC_MRU, \
USE_SLACK, SLACK_NOTIFY_ONSNATCH, SLACK_NOTIFY_ONDOWNLOAD, SLACK_NOTIFY_ONSUBTITLEDOWNLOAD, \
SLACK_CHANNEL, SLACK_AS_AUTHED, SLACK_BOT_NAME, SLACK_ICON_URL, SLACK_ACCESS_TOKEN, \
USE_DISCORDAPP, DISCORDAPP_NOTIFY_ONSNATCH, DISCORDAPP_NOTIFY_ONDOWNLOAD, \
@ -1139,6 +1140,7 @@ def init_stage_1(console_logging):
TRAKT_MRU = check_setting_str(CFG, 'Trakt', 'trakt_mru', '')
MC_MRU = check_setting_str(CFG, 'Metacritic', 'mc_mru', '')
TVC_MRU = check_setting_str(CFG, 'TVCalendar', 'tvc_mru', '')
USE_PYTIVO = bool(check_setting_int(CFG, 'pyTivo', 'use_pytivo', 0))
PYTIVO_HOST = check_setting_str(CFG, 'pyTivo', 'pytivo_host', '')
@ -2055,6 +2057,9 @@ def save_config():
('Metacritic', [
('mru', MC_MRU)
]),
('TVCalendar', [
('mru', TVC_MRU)
]),
('Slack', [
('use_%s', int(USE_SLACK)),
('channel', SLACK_CHANNEL),

220
sickbeard/webserve.py

@ -220,7 +220,7 @@ class RouteHandler(LegacyBaseHandler):
method_args += [item for item in arg if None is not item]
if 'kwargs' in method_args or re.search('[A-Z]', route):
# no filtering for legacy and routes that depend on *args and **kwargs
result = yield self.async_call(method, request_kwargs) # method(**request_kwargs)
result = yield self.async_call(method, request_kwargs) # method(**request_kwargs)
else:
filter_kwargs = dict(filter_iter(lambda kv: kv[0] in method_args, iteritems(request_kwargs)))
result = yield self.async_call(method, filter_kwargs) # method(**filter_kwargs)
@ -810,8 +810,8 @@ class LoadingWebHandler(BaseHandler):
@authenticated
@gen.coroutine
def get(self, route, *args, **kwargs):
yield self.route_method(route, use_404=True, limit_route=(lambda _route: not re.search('get[_-]message', _route)
and 'loading-page' or _route))
yield self.route_method(route, use_404=True, limit_route=(
lambda _route: not re.search('get[_-]message', _route) and 'loading-page' or _route))
post = get
@ -4411,6 +4411,220 @@ class AddShows(Home):
if not filter_list(lambda tvid_prodid: helpers.find_show_by_id(tvid_prodid), ids.split(' ')):
return self.new_show('|'.join(['', '', '', ids or show_name]), use_show_name=True)
def tvc_default(self):
return self.redirect('/add-shows/%s' % ('tvc_newshows', sickbeard.TVC_MRU)[any(sickbeard.TVC_MRU)])
def tvc_newshows(self, **kwargs):
return self.browse_tvc(
'TV-shows-starting-', 'New Shows at TV Calendar', mode='newshows', **kwargs)
def tvc_returning(self, **kwargs):
return self.browse_tvc(
'TV-shows-starting-', 'Returning Shows at TV Calendar', mode='returning', **kwargs)
def tvc_latest(self, **kwargs):
return self.browse_tvc(
'recent-additions', 'Latest new shows at TV Calendar', mode='latest', **kwargs)
def browse_tvc(self, url_path, browse_title, **kwargs):
browse_type = 'TVCalendar'
footnote = None
filtered = []
today = datetime.datetime.today()
months = ['', 'January', 'February', 'March', 'April', 'May', 'June', 'July',
'August', 'September', 'October', 'November', 'December']
this_month = '%s-%s' % (months[today.month], today.strftime('%Y'))
section = ''
if kwargs.get('mode', '') in ('newshows', 'returning'):
section = (kwargs.get('page') or this_month)
url_path += section
import browser_ua
url = 'https://www.pogdesign.co.uk/cat/%s' % url_path
html = helpers.get_url(url, headers={'User-Agent': browser_ua.get_ua()})
use_votes = False
if html:
prev_month = dt_prev_month = None
if kwargs.get('mode', '') in ('newshows', 'returning'):
try:
prev_month = re.findall('(?i)href="/cat/tv-shows-starting-([^"]+)', html)[0]
dt_prev_month = dateutil.parser.parse('1-%s' % prev_month)
except (BaseException, Exception):
prev_month = None
get_prev_month = (lambda _dt: _dt.replace(day=1) - datetime.timedelta(days=1))
get_next_month = (lambda _dt: _dt.replace(day=28) + datetime.timedelta(days=5))
get_month_year = (lambda _dt: '%s-%s' % (months[_dt.month], _dt.strftime('%Y')))
if prev_month:
dt_next_month = get_next_month(dt_prev_month)
while True:
next_month = get_month_year(dt_next_month)
if next_month not in (this_month, kwargs.get('page')):
break
dt_next_month = get_next_month(dt_next_month)
while True:
if prev_month not in (this_month, kwargs.get('page')):
break
dt_prev_month = get_prev_month(dt_prev_month)
prev_month = get_month_year(dt_prev_month)
else:
dt_next_month = get_next_month(today)
next_month = get_month_year(dt_next_month)
dt_prev_month = get_prev_month(today)
prev_month = get_month_year(dt_prev_month)
suppress_item = not kwargs.get('page') or kwargs.get('page') == this_month
get_date_text = (lambda m_y: m_y.replace('-', ' '))
next_month_text = get_date_text(next_month)
prev_month_text = get_date_text(prev_month)
page_month_text = suppress_item and 'void' or get_date_text(kwargs.get('page'))
kwargs.update(dict(pages=[
('tvc_newshows?page=%s' % this_month, 'New this month'),
('tvc_newshows?page=%s' % next_month, '...in %s' % next_month_text)] +
([('tvc_newshows?page=%s' % kwargs.get('page'), '...in %s' % page_month_text)], [])[suppress_item] +
[('tvc_newshows?page=%s' % prev_month, '...in %s' % prev_month_text)] +
[('tvc_returning?page=%s' % this_month, 'Returning this month'),
('tvc_returning?page=%s' % next_month, '...in %s' % next_month_text)] +
([('tvc_returning?page=%s' % kwargs.get('page'), '...in %s' % page_month_text)], [])[suppress_item] +
[('tvc_returning?page=%s' % prev_month, '...in %s' % prev_month_text)]
))
with BS4Parser(html, parse_only=dict(div={'class': (lambda at: at and 'pgwidth' in at)})) as tbl:
shows = []
if kwargs.get('mode', '') in ('latest', 'newshows', 'returning'):
tags = tbl.select('h2[class*="midtitle"], div[class*="contbox"]')
collect = False
for cur_tag in tags:
if re.match(r'(?i)h\d+', cur_tag.name) and 'midtitle' in cur_tag.attrs.get('class', []):
text = cur_tag.get_text(strip=True)
if kwargs['mode'] in ('latest', 'newshows'):
if not collect and ('Latest' in text or 'New' in text):
collect = True
continue
break
elif 'New' in text:
continue
elif 'Return' in text:
collect = True
continue
break
if collect:
shows += [cur_tag]
if not len(shows):
kwargs['error_msg'] = 'No TV titles found in <a target="_blank" href="%s">%s</a>%s,' \
' try another selection' % (
helpers.anon_url(url), browse_title, (' for %s' % section.replace('-', ' '), '')[not section])
# build batches to correct htmlentity typos in overview
from html5lib.constants import entities
batches = []
for cur_n, cur_name in enumerate(entities):
if 0 == cur_n % 150:
if cur_n:
batches += [batch]
batch = []
batch += [cur_name]
else:
batches += [batch]
oldest, newest, oldest_dt, newest_dt = None, None, 9999999, 0
for row in shows:
try:
ids = dict(custom=row.select('input[type="checkbox"]')[0].attrs['value'], name='tvc')
info = row.find('a', href=re.compile('^/cat'))
url_path = info['href'].strip()
title = info.find('h2').get_text(strip=True)
img_uri = info.get('data-original', '').strip()
if not img_uri:
img_uri = re.findall(r'(?i).*?image:\s*url\(([^)]+)', info.attrs['style'])[0].strip()
images = dict(poster=dict(thumb='imagecache?path=browse/thumb/tvc&source=%s' % img_uri))
sickbeard.CACHE_IMAGE_URL_LIST.add_url(img_uri)
title = re.sub(r'(?i)(?::\s*season\s*\d+|\s*\((?:19|20)\d{2}\))?$', '', title.strip())
dt_ordinal = 0
dt_string = ''
date = genre = network = ''
date_tag = row.find('span', class_='startz')
if date_tag:
date_network = re.split(r'(?i)\son\s', ''.join(
[t.name and t.get_text() or str(t) for t in date_tag][0:2]))
date = re.sub('(?i)^(starts|returns)', '', date_network[0]).strip()
network = ('', date_network[1].strip())[2 == len(date_network)]
else:
date_tag = row.find('span', class_='selby')
if date_tag:
date = date_tag.get_text(strip=True)
network_genre = info.find('span')
if network_genre:
network_genre = network_genre.get_text(strip=True).split('//')
network = network_genre[0]
genre = ('', network_genre[1].strip())[2 == len(network_genre)]
if date:
dt = dateutil.parser.parse(date)
dt_ordinal = dt.toordinal()
dt_string = SGDatetime.sbfdate(dt)
if dt_ordinal < oldest_dt:
oldest_dt = dt_ordinal
oldest = dt_string
if dt_ordinal > newest_dt:
newest_dt = dt_ordinal
newest = dt_string
overview = row.find('span', class_='shwtxt')
if overview:
overview = re.sub(r'(?sim)(.*?)(?:[.\s]*\*\*NOTE.*?)?(\.{1,3})$', r'\1\2',
overview.get_text(strip=True))
for cur_entities in batches:
overview = re.sub(r'and(%s)' % '|'.join(cur_entities), r'&\1', overview)
votes = None
votes_tag = row.find('span', class_='selby')
if votes_tag:
votes_tag = votes_tag.find('strong')
if votes_tag:
votes = re.sub(r'(?i)\s*users', '', votes_tag.get_text()).strip()
use_votes = True
filtered.append(dict(
premiered=dt_ordinal,
premiered_str=dt_string,
when_past=dt_ordinal < datetime.datetime.now().toordinal(),
genres=genre,
network=network or None,
ids=ids,
images='' if not img_uri else images,
overview='No overview yet' if not overview else helpers.xhtml_escape(overview[:250:]),
rating=None,
title=title,
url_src_db='https://www.pogdesign.co.uk/%s' % url_path.strip('/'),
votes=votes or 'n/a'))
except (AttributeError, IndexError, KeyError, TypeError):
continue
kwargs.update(dict(oldest=oldest, newest=newest))
kwargs.update(dict(footnote=footnote, use_ratings=False, use_votes=use_votes, show_header=True))
mode = kwargs.get('mode', '')
if mode:
func = 'tvc_%s' % mode
if callable(getattr(self, func, None)):
sickbeard.TVC_MRU = func
sickbeard.save_config()
return self.browse_shows(browse_type, browse_title, filtered, **kwargs)
def info_tvcalendar(self, ids, show_name):
return self.new_show('|'.join(['', '', '', show_name]), use_show_name=True)
def mc_default(self):
return self.redirect('/add-shows/%s' % ('mc_newseries', sickbeard.MC_MRU)[any(sickbeard.MC_MRU)])

Loading…
Cancel
Save