diff --git a/CHANGES.md b/CHANGES.md index a0ccb334..76a763a8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -19,6 +19,12 @@ * Add search of grouped options in shows drop down at view-show +### 3.29.10 (2023-09-19 12:55:00 UTC) + +* Fix Metacritic cards +* Fix Linux CI tests + + ### 3.29.9 (2023-09-17 23:25:00 UTC) * Fix Emby notifier library update diff --git a/gui/slick/interfaces/default/home_browseShows.tmpl b/gui/slick/interfaces/default/home_browseShows.tmpl index ee6b3845..e3b02f74 100644 --- a/gui/slick/interfaces/default/home_browseShows.tmpl +++ b/gui/slick/interfaces/default/home_browseShows.tmpl @@ -418,12 +418,12 @@ $(document).ready(function(){ #elif 'Metacritic' == $browse_type - - - - - #if $kwargs and $kwargs.get('more') and 'newseries' in $mode - + + + + + #if $kwargs and $kwargs.get('more') + #end if #elif 'TMDB' == $browse_type diff --git a/sickgear/webserve.py b/sickgear/webserve.py index e76b2f45..79f10a12 100644 --- a/sickgear/webserve.py +++ b/sickgear/webserve.py @@ -5002,23 +5002,23 @@ class AddShows(Home): def mc_newseries(self, **kwargs): return self.browse_mc( - 'release-date/new-series/date?', 'New Series at Metacritic', mode='newseries', **kwargs) + '/all/all/all-time/new/', 'New Series at Metacritic', mode='newseries', **kwargs) - def mc_90days(self, **kwargs): + def mc_explore(self, **kwargs): return self.browse_mc( - 'score/metascore/90day/filtered?sort=desc&', 'Last 90 days at Metacritic', mode='90days', **kwargs) + '/', 'Explore at Metacritic', mode='explore', **kwargs) - def mc_year(self, **kwargs): + def mc_popular(self, **kwargs): return self.browse_mc( - 'score/metascore/year/filtered?sort=desc&', 'By year at Metacritic', mode='year', **kwargs) + '/all/all/all-time/popular/', 'Popular at Metacritic', mode='popular', **kwargs) - def mc_discussed(self, **kwargs): + def mc_metascore(self, **kwargs): return self.browse_mc( - 'score/metascore/discussed/filtered?sort=desc&', 'Most discussed at Metacritic', mode='discussed', **kwargs) + '/all/all/all-time/metascore/', 'By metascore at Metacritic', mode='metascore', **kwargs) - def mc_shared(self, **kwargs): + def mc_userscore(self, **kwargs): return self.browse_mc( - 'score/metascore/shared/filtered?sort=desc&', 'Most shared at Metacritic', mode='shared', **kwargs) + '/all/all/all-time/userscore/', 'By userscore at Metacritic', mode='userscore', **kwargs) def browse_mc(self, url_path, browse_title, **kwargs): @@ -5026,76 +5026,86 @@ class AddShows(Home): footnote = None - page = 'more' in kwargs and '&page=1' or '' + page = 'more' in kwargs and '&page=2' or '' if page: kwargs['mode'] += '-more' filtered = [] import browser_ua - url = 'https://www.metacritic.com/browse/tv/%sview=detailed%s' % (url_path, page) + this_year = datetime.datetime.today().strftime('%Y') + url = f'https://www.metacritic.com/browse/tv{url_path}' \ + f'?releaseYearMin={this_year}&releaseYearMax={this_year}{page}' html = helpers.get_url(url, headers={'User-Agent': browser_ua.get_ua()}) if html: try: - if re.findall('(]+rel="next"[^>]+)', html)[0]: + if re.findall('(c-navigationPagination_item--next)', html)[0]: kwargs.update(dict(more=1)) except (BaseException, Exception): pass - with BS4Parser(html, parse_only=dict(table={'class': (lambda at: at and 'clamp-list' in at)})) as tbl: - # with BS4Parser(html, features=['html5lib', 'permissive']) as soup: - items = [] if not tbl else tbl.find_all('tr') + with BS4Parser(html, parse_only=dict(div={'class': (lambda at: at and 'c-productListings' in at)})) as soup: + items = [] if not soup else soup.select('.c-finderProductCard_container') oldest, newest, oldest_dt, newest_dt = None, None, 9999999, 0 - for cur_row in items: + rc_title = re.compile(r'(?i)(?::\s*season\s*\d+|\s*\((?:19|20)\d{2}\))?$') + rc_id = re.compile(r'(?i)[^A-Z0-9]') + rc_img = re.compile(r'(.*?)(/resize/[^?]+)?(/catalog/provider.*?\.(?:jpg|png)).*') + rc_season = re.compile(r'(\d+)(?:[.]\d*?)?$') + for idx, cur_row in enumerate(items): try: - ids = dict(custom=cur_row.select('input[type="checkbox"]')[0].attrs['id'], name='mc') - info = cur_row.find('a', href=re.compile('^/tv')) - url_path = info['href'].strip() + title = rc_title.sub( + '', cur_row.find('div', class_='c-finderProductCard_title').get('data-title').strip()) - images = {} - img_uri = None - img = info.find('img') - if img and isinstance(img.attrs, dict): - title = img.attrs.get('alt') - img_src = img.attrs.get('src').split('-') - img_uri = img_src.pop(0) - if img_src: - img_uri += '.' + img_src[0].split('.')[-1] - images = dict(poster=dict(thumb='imagecache?path=browse/thumb/metac&source=%s' % img_uri)) + # 2023-09-23 deprecated id at site, using title as id + # ids = dict(custom=cur_row.select('input[type="checkbox"]')[0].attrs['id'], name='mc') + ids = dict(custom=rc_id.sub('', title), name='mc') + + url_path = cur_row['href'].strip() + if not url_path.startswith('/tv/'): + continue + + images = None + img_src = (cur_row.find('img') or {}).get('src', '').strip() + if img_src: + img_uri = rc_img.sub(r'\1\3', img_src) + images = dict(poster=dict(thumb=f'imagecache?path=browse/thumb/metac&source={img_uri}')) sickgear.CACHE_IMAGE_URL_LIST.add_url(img_uri) - if not title: - title = cur_row.find('h3').get_text() - title = re.sub(r'(?i)(?::\s*season\s*\d+|\s*\((?:19|20)\d{2}\))?$', '', title.strip()) ord_premiered = 0 str_premiered = '' started_past = False - date_tags = list(filter(lambda t: t.find('span'), - cur_row.find_all('div', class_='clamp-details'))) - if date_tags: - ord_premiered, str_premiered, started_past, oldest_dt, newest_dt, oldest, newest, \ - _, _, _, _ \ - = self.sanitise_dates(date_tags[0].get_text().strip(), oldest_dt, newest_dt, - oldest, newest) - overview = cur_row.find('div', class_='summary').get_text().strip() + dated = None + rating = None + rating_user = None # 2023-09-23 deprecated at site + meta_tags = cur_row.find_all('div', class_='c-finderProductCard_meta') + for tag in meta_tags: + meta_tag = tag.find('span', class_='u-text-uppercase') + if not dated and meta_tag: + dated = meta_tag + try: # a bad date caused a sanitise exception here + ord_premiered, str_premiered, started_past, oldest_dt, newest_dt, oldest, newest, \ + _, _, _, _ = self.sanitise_dates(dated.get_text().strip(), oldest_dt, newest_dt, + oldest, newest) + except (BaseException, Exception): + pass - rating = cur_row.find('div', class_='clamp-metascore') - if rating: - rating = rating.find('div', class_='metascore_w') - if rating: + meta_tag = tag.find('div', class_='c-siteReviewScore') + if not rating and meta_tag: + rating = meta_tag rating = rating.get_text().strip() - rating_user = cur_row.find('div', class_='clamp-userscore') - if rating_user: - rating_user = rating_user.find('div', class_='metascore_w') - if rating_user: - rating_user = rating_user.get_text().strip() - season = -1 + if dated and rating: + break + + overview = cur_row.find('div', class_='c-finderProductCard_description') + if overview: + overview = helpers.xhtml_escape(overview.get_text().strip()[:250:]) + try: - season = re.findall(r'(\d+)(?:[.]\d*?)?$', url_path)[0] + season = rc_season.findall(url_path)[0] except(BaseException, Exception): - pass + season = -1 filtered.append(dict( ord_premiered=ord_premiered, @@ -5104,12 +5114,12 @@ class AddShows(Home): episode_season=int(season), genres='', ids=ids, - images='' if not img_uri else images, - overview='No overview yet' if not overview else helpers.xhtml_escape(overview[:250:]), + images=images or '', + overview=overview or 'No overview yet', rating=0 if not rating else rating or 'TBD', rating_user='tbd' if not rating_user else int(helpers.try_float(rating_user) * 10) or 'tbd', title=title, - url_src_db='https://www.metacritic.com/%s/' % url_path.strip('/'), + url_src_db=f'https://www.metacritic.com/{url_path.strip("/")}/', votes=None)) except (AttributeError, IndexError, KeyError, TypeError): @@ -5121,7 +5131,7 @@ class AddShows(Home): mode = kwargs.get('mode', '') if mode: - func = 'mc_%s' % mode + func = f'mc_{mode}' if callable(getattr(self, func, None)): sickgear.MC_MRU = func sickgear.save_config() diff --git a/tests/show_tests.py b/tests/show_tests.py index a47c878f..b4661571 100644 --- a/tests/show_tests.py +++ b/tests/show_tests.py @@ -18,8 +18,8 @@ import datetime import unittest +import test_lib as test # must be before import sickgear import sickgear -import test_lib as test from sickgear import db from sickgear.common import Quality, UNAIRED, SKIPPED, WANTED, WantedQualities, statusStrings from sickgear.show_queue import QueueItemAdd