diff --git a/CHANGES.md b/CHANGES.md index 8916afe8..8bc988bb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,17 @@ -### 3.32.7 (2024-08-13 11:30:00 UTC) +### 3.32.8 (2024-10-07 00:30:00 UTC) + +* Change min required Python version to 3.9 +* Change add support for Python 3.9.20, 3.10.15, 3.11.10, 3.12.7 +* Change update fallback zoneinfo to 2024b +* Change improve config.ini save failure messages +* Change add '-f' to copy_file command for 'posix' systems +* Fix Fanart.tv and CF data fetch +* Fix TVC images +* Change add '.avif' extensions as valid image type (used by tvc cards) +* Change hide lazy load animation when loading fails (unsupported image format) + + +### 3.32.7 (2024-08-13 11:30:00 UTC) * Change to prevent saving config.ini before it's fully loaded * Change login form to be more password manager friendly diff --git a/gui/slick/css/style.css b/gui/slick/css/style.css index 9b7fdebe..92d1973f 100644 --- a/gui/slick/css/style.css +++ b/gui/slick/css/style.css @@ -1485,6 +1485,8 @@ home_browseShows.tmpl border-top-left-radius:5px; border-top-right-radius:5px; border-bottom:1px solid #111; + background-position:50% 50%; + text-decoration: none !important; background-image:url("../images/poster-dark.jpg") } diff --git a/gui/slick/js/inc_bottom.js b/gui/slick/js/inc_bottom.js index 2b954742..e9f24ef4 100644 --- a/gui/slick/js/inc_bottom.js +++ b/gui/slick/js/inc_bottom.js @@ -1,14 +1,17 @@ -function initLazyload(){ - $.ll = new LazyLoad({elements_selector:'img[data-original]', callback_load:function(element){ - if (element.id) { - var el = document.getElementById('loading-' + element.id), className = 'hide'; - if (!!document.body.classList) { - el.classList.add(className); - } else { - el.className += (el.className ? ' ' : '') + className; - } +function hide_lazy_ani (element){ + if (element.id) { + var el = document.getElementById('loading-' + element.id), className = 'hide'; + if (!!document.body.classList) { + el.classList.add(className); + } else { + el.className += (el.className ? ' ' : '') + className; } - }}); + } +} + +function initLazyload(){ + $.ll = new LazyLoad({elements_selector:'img[data-original]', callback_load: hide_lazy_ani, + callback_error: hide_lazy_ani}); $.ll.handleScroll(); return !0; } diff --git a/lib/cfscrape/__init__.py b/lib/cfscrape/__init__.py index bc80d22e..1983cd95 100644 --- a/lib/cfscrape/__init__.py +++ b/lib/cfscrape/__init__.py @@ -56,8 +56,8 @@ class CloudflareScraper(Session): and resp.status_code in (503, 429, 403)): self.start_time = time.time() if (re.search('(?i)cloudflare', resp.headers.get('Server', '')) - and b'jschl_vc' in resp.content - and b'jschl_answer' in resp.content): + and b'_cf_chl_' in resp.content + or (b'jschl_vc' in resp.content and b'jschl_answer' in resp.content)): resp = self.solve_cf_challenge(resp, url_solver, **kwargs) elif b'ddgu' in resp.content: resp = self.solve_ddg_challenge(resp, **kwargs) diff --git a/lib/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz b/lib/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz index fef226e8..fa2f878d 100644 Binary files a/lib/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz and b/lib/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz differ diff --git a/lib/fanart/core.py b/lib/fanart/core.py index 24f7274e..7358f1e6 100644 --- a/lib/fanart/core.py +++ b/lib/fanart/core.py @@ -7,11 +7,12 @@ from sg_helpers import get_url class Request(object): - def __init__(self, apikey, tvdb_id, ws=fanart.WS.TV, types=None): + def __init__(self, apikey, tvdb_id, ws=fanart.WS.TV, types=None, **kwargs): self._apikey = apikey self._tvdb_id = tvdb_id self._ws = ws self._types = types + self._kwargs = kwargs self._response = None self._web_url = 'https://fanart.tv/series/%s' self._assets_url = 'https://assets.fanart.tv' @@ -22,7 +23,7 @@ class Request(object): def response(self): try: - rjson = get_url(str(self), parse_json=True) + rjson = get_url(str(self), parse_json=True, **self._kwargs) image_type = self._types or u'showbackground' rhtml = self.scrape_web(image_type) if not isinstance(rjson, dict) and 0 == len(rhtml[image_type]): @@ -31,7 +32,7 @@ class Request(object): if not isinstance(rjson, dict): rjson = {image_type: []} - if 0 != len(rhtml[image_type]): + if None is not rhtml and 0 != len(rhtml[image_type]): rjson_ids = map(lambda i: i['id'], rjson[image_type]) for item in filter(lambda i: i['id'] not in rjson_ids, rhtml[image_type]): rjson[image_type] += [item] @@ -48,7 +49,7 @@ class Request(object): def scrape_web(self, image_type): try: - data = get_url(self._web_url % self._tvdb_id) + data = get_url(self._web_url % self._tvdb_id, **self._kwargs) if not data: return diff --git a/lib/sg_helpers.py b/lib/sg_helpers.py index 08c2d416..52f600ad 100644 --- a/lib/sg_helpers.py +++ b/lib/sg_helpers.py @@ -1148,7 +1148,7 @@ def scantree(path, # type: AnyStr def copy_file(src_file, dest_file): if os.name.startswith('posix'): - subprocess.call(['cp', src_file, dest_file]) + subprocess.call(['cp', '-f', src_file, dest_file]) else: shutil.copyfile(src_file, dest_file) diff --git a/sickgear.py b/sickgear.py index 24d981f7..b30fef9d 100755 --- a/sickgear.py +++ b/sickgear.py @@ -36,9 +36,8 @@ warnings.filterwarnings('ignore', module=r'.*ssl_.*', message='.*SSLContext obje warnings.filterwarnings('ignore', module=r'.*zoneinfo.*', message='.*file or directory.*') warnings.filterwarnings('ignore', message='.*deprecated in cryptography.*') -versions = [((3, 8, 2), (3, 8, 19)), - ((3, 9, 0), (3, 9, 2)), ((3, 9, 4), (3, 9, 19)), - ((3, 10, 0), (3, 12, 5))] # inclusive version ranges +versions = [((3, 9, 0), (3, 9, 2)), ((3, 9, 4), (3, 9, 20)), + ((3, 10, 0), (3, 12, 7))] # inclusive version ranges if not any(list(map(lambda v: v[0] <= sys.version_info[:3] <= v[1], versions))) and not int(os.environ.get('PYT', 0)): major, minor, micro = sys.version_info[:3] print('Python %s.%s.%s detected.' % (major, minor, micro)) diff --git a/sickgear/__init__.py b/sickgear/__init__.py index 1f7eee64..82e3b32d 100644 --- a/sickgear/__init__.py +++ b/sickgear/__init__.py @@ -2475,22 +2475,36 @@ def _save_config(force=False, **kwargs): backup_config = re.sub(r'\.ini$', '.bak', CONFIG_FILE) from .config import check_valid_config try: + if check_valid_config(CONFIG_FILE): + for _t in range(0, 3): copy_file(CONFIG_FILE, backup_config) if not check_valid_config(backup_config): - logger.error('config file seams to be invalid, not backing up.') + if 2 > _t: + logger.debug('backup config file seems to be invalid, retrying...') + else: + logger.warning('backup config file seems to be invalid, not backing up.') + backup_config = None remove_file_perm(backup_config) + 2 > _t and time.sleep(3) + else: + break + else: + logger.warning('existing config file is invalid, not backing it up') backup_config = None except (BaseException, Exception): backup_config = None - for _ in range(0, 3): + for _t in range(0, 3): new_config.write() if check_valid_config(CONFIG_FILE): CONFIG_OLD = copy.deepcopy(new_config) return - logger.warning('saving config file failed, retrying...') + if 2 > _t: + logger.debug('saving config file failed, retrying...') + else: + logger.warning('saving config file failed.') remove_file_perm(CONFIG_FILE) - time.sleep(3) + 2 > _t and time.sleep(3) # we only get here if the config saving failed multiple times if None is not backup_config and os.path.isfile(backup_config): diff --git a/sickgear/config.py b/sickgear/config.py index 214c021c..c951b399 100644 --- a/sickgear/config.py +++ b/sickgear/config.py @@ -484,7 +484,7 @@ def backup_config(): logger.log('backing up config.ini') try: if not check_valid_config(sickgear.CONFIG_FILE): - logger.error('config file seams to be invalid, not backing up.') + logger.error('config file seems to be invalid, not backing up.') return now = datetime.datetime.now() d = datetime.datetime.strftime(now, '%Y-%m-%d') @@ -493,7 +493,7 @@ def backup_config(): target = os.path.join(target_base, 'config.ini') copy_file(sickgear.CONFIG_FILE, target) if not check_valid_config(target): - logger.error('config file seams to be invalid, not backing up.') + logger.error('config file seems to be invalid, not backing up.') remove_file_perm(target) return compress_file(target, 'config.ini') diff --git a/sickgear/helpers.py b/sickgear/helpers.py index 9c682585..4f765cb2 100644 --- a/sickgear/helpers.py +++ b/sickgear/helpers.py @@ -171,7 +171,7 @@ def has_image_ext(filename): :rtype: bool """ try: - if os.path.splitext(filename)[1].lower() in ['.bmp', '.gif', '.jpeg', '.jpg', '.png', '.webp']: + if os.path.splitext(filename)[1].lower() in ['.avif', '.bmp', '.gif', '.jpeg', '.jpg', '.png', '.webp']: return True except (BaseException, Exception): pass diff --git a/sickgear/metadata/generic.py b/sickgear/metadata/generic.py index 3252f2cb..02ce5590 100644 --- a/sickgear/metadata/generic.py +++ b/sickgear/metadata/generic.py @@ -1198,7 +1198,8 @@ class GenericMetadata(object): try: if tvdb_id: - request = fanartRequest(apikey=sickgear.FANART_API_KEY, tvdb_id=tvdb_id, types=types[image_type]) + request = fanartRequest(apikey=sickgear.FANART_API_KEY, tvdb_id=tvdb_id, types=types[image_type], + url_solver=sickgear.FLARESOLVERR_HOST) resp = request.response() itemlist = [] dedupe = [] diff --git a/sickgear/py_requirement.data b/sickgear/py_requirement.data index 00e897bd..b72ad011 100644 --- a/sickgear/py_requirement.data +++ b/sickgear/py_requirement.data @@ -1 +1 @@ -3.8.2 \ No newline at end of file +3.9.0 \ No newline at end of file diff --git a/sickgear/webserve.py b/sickgear/webserve.py index 16b1d53d..a1f927a0 100644 --- a/sickgear/webserve.py +++ b/sickgear/webserve.py @@ -5936,9 +5936,19 @@ class AddShows(Home): 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() + img_uri = None + # try image locations e.g. https://pogd.es/assets/bg/KAOS.jpg + img_name = re.sub(r'[:\s]+', '-', title) + for cur_type in ('jpg', 'jpeg', 'webp', 'png', 'gif', 'bmp', 'avif'): + uri = f'https://pogd.es/assets/bg/{img_name}.{cur_type}' + if helpers.check_url(uri): + img_uri = uri + break + if None is img_uri: + # use alternative avif image fallback as only supported by new browsers + img_uri = info.get('data-original', '').strip() + if not img_uri: # old image fallback (pre 2024-08-18) + 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)) sickgear.CACHE_IMAGE_URL_LIST.add_url(img_uri) title = re.sub(r'(?i)(?::\s*season\s*\d+|\s*\((?:19|20)\d{2}\))?$', '', title.strip()) diff --git a/tests/test_lib.py b/tests/test_lib.py index 8fefd243..1edbb90d 100644 --- a/tests/test_lib.py +++ b/tests/test_lib.py @@ -206,7 +206,7 @@ def setup_test_db(): def teardown_test_db(): """Deletes the test db - although this seams not to work on my system it leaves me with an zero kb file + although this seems not to work on my system it leaves me with an zero kb file """ # uncomment next line so leave the db intact between test and at the end # return False