diff --git a/CHANGES.md b/CHANGES.md
index f35c59a..9a49489 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,4 +1,11 @@
-### 0.21.26 (2020-04-13 00:30:00 UTC)
+### 0.21.27 (2020-04-22 02:50:00 UTC)
+
+* Update TZlocal 2.0.0b3 (410a838) to 2.1b1 (dd79171)
+* Change Emby notifier to add unofficial support for Jellyfin
+* Change Filelist torrent provider
+
+
+### 0.21.26 (2020-04-13 00:30:00 UTC)
* Fix AttributeError in anime manager while editing show (part deux)
* Fix use lib logger instead of global logger
diff --git a/HACKS.txt b/HACKS.txt
index 9b331cc..2de46cc 100644
--- a/HACKS.txt
+++ b/HACKS.txt
@@ -25,4 +25,3 @@ Libs with customisations...
/lib/tmdbsimple
/lib/tornado
/lib/tvdb_api/tvdb_api.py
-/lib/tzlocal/unix.py
diff --git a/lib/tzlocal/unix.py b/lib/tzlocal/unix.py
index 388273c..8574965 100644
--- a/lib/tzlocal/unix.py
+++ b/lib/tzlocal/unix.py
@@ -138,7 +138,7 @@ def _get_localzone(_root='/'):
if os.path.exists(tzpath) and os.path.islink(tzpath):
tzpath = os.path.realpath(tzpath)
start = tzpath.find("/")+1
- while start is not 0:
+ while start != 0:
tzpath = tzpath[start:]
try:
return pytz.timezone(tzpath)
diff --git a/lib/tzlocal/utils.py b/lib/tzlocal/utils.py
index bd9d663..5a67799 100644
--- a/lib/tzlocal/utils.py
+++ b/lib/tzlocal/utils.py
@@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-
+import time
import datetime
+import calendar
def get_system_offset():
@@ -11,8 +13,14 @@ def get_system_offset():
To keep compatibility with Windows, we're always importing time module here.
"""
- import time
- if time.daylight and time.localtime().tm_isdst > 0:
+
+ localtime = calendar.timegm(time.localtime())
+ gmtime = calendar.timegm(time.gmtime())
+ offset = gmtime - localtime
+ # We could get the localtime and gmtime on either side of a second switch
+ # so we check that the difference is less than one minute, because nobody
+ # has that small DST differences.
+ if abs(offset - time.altzone) < 60:
return -time.altzone
else:
return -time.timezone
diff --git a/lib/tzlocal/windows_tz.py b/lib/tzlocal/windows_tz.py
index 3d691c8..86ba807 100644
--- a/lib/tzlocal/windows_tz.py
+++ b/lib/tzlocal/windows_tz.py
@@ -87,6 +87,7 @@ win_tz = {'AUS Central Standard Time': 'Australia/Darwin',
'Pacific Standard Time (Mexico)': 'America/Tijuana',
'Pakistan Standard Time': 'Asia/Karachi',
'Paraguay Standard Time': 'America/Asuncion',
+ 'Qyzylorda Standard Time': 'Asia/Qyzylorda',
'Romance Standard Time': 'Europe/Paris',
'Russia Time Zone 10': 'Asia/Srednekolymsk',
'Russia Time Zone 11': 'Asia/Kamchatka',
@@ -127,6 +128,7 @@ win_tz = {'AUS Central Standard Time': 'Australia/Darwin',
'Ulaanbaatar Standard Time': 'Asia/Ulaanbaatar',
'Venezuela Standard Time': 'America/Caracas',
'Vladivostok Standard Time': 'Asia/Vladivostok',
+ 'Volgograd Standard Time': 'Europe/Volgograd',
'W. Australia Standard Time': 'Australia/Perth',
'W. Central Africa Standard Time': 'Africa/Lagos',
'W. Europe Standard Time': 'Europe/Berlin',
@@ -287,7 +289,7 @@ tz_win = {'Africa/Abidjan': 'Greenwich Standard Time',
'America/Mendoza': 'Argentina Standard Time',
'America/Menominee': 'Central Standard Time',
'America/Merida': 'Central Standard Time (Mexico)',
- 'America/Metlakatla': 'Pacific Standard Time',
+ 'America/Metlakatla': 'Alaskan Standard Time',
'America/Mexico_City': 'Central Standard Time (Mexico)',
'America/Miquelon': 'Saint Pierre Standard Time',
'America/Moncton': 'Atlantic Standard Time',
@@ -347,13 +349,13 @@ tz_win = {'Africa/Abidjan': 'Greenwich Standard Time',
'America/Winnipeg': 'Central Standard Time',
'America/Yakutat': 'Alaskan Standard Time',
'America/Yellowknife': 'Mountain Standard Time',
- 'Antarctica/Casey': 'W. Australia Standard Time',
+ 'Antarctica/Casey': 'Singapore Standard Time',
'Antarctica/Davis': 'SE Asia Standard Time',
'Antarctica/DumontDUrville': 'West Pacific Standard Time',
'Antarctica/Macquarie': 'Central Pacific Standard Time',
'Antarctica/Mawson': 'West Asia Standard Time',
'Antarctica/McMurdo': 'New Zealand Standard Time',
- 'Antarctica/Palmer': 'Magallanes Standard Time',
+ 'Antarctica/Palmer': 'SA Eastern Standard Time',
'Antarctica/Rothera': 'SA Eastern Standard Time',
'Antarctica/South_Pole': 'New Zealand Standard Time',
'Antarctica/Syowa': 'E. Africa Standard Time',
@@ -424,7 +426,7 @@ tz_win = {'Africa/Abidjan': 'Greenwich Standard Time',
'Asia/Pyongyang': 'North Korea Standard Time',
'Asia/Qatar': 'Arab Standard Time',
'Asia/Qostanay': 'Central Asia Standard Time',
- 'Asia/Qyzylorda': 'West Asia Standard Time',
+ 'Asia/Qyzylorda': 'Qyzylorda Standard Time',
'Asia/Rangoon': 'Myanmar Standard Time',
'Asia/Riyadh': 'Arab Standard Time',
'Asia/Saigon': 'SE Asia Standard Time',
@@ -592,7 +594,7 @@ tz_win = {'Africa/Abidjan': 'Greenwich Standard Time',
'Europe/Vatican': 'W. Europe Standard Time',
'Europe/Vienna': 'W. Europe Standard Time',
'Europe/Vilnius': 'FLE Standard Time',
- 'Europe/Volgograd': 'Russian Standard Time',
+ 'Europe/Volgograd': 'Volgograd Standard Time',
'Europe/Warsaw': 'Central European Standard Time',
'Europe/Zagreb': 'Central European Standard Time',
'Europe/Zaporozhye': 'FLE Standard Time',
diff --git a/sickbeard/notifiers/emby.py b/sickbeard/notifiers/emby.py
index b21452d..b59d647 100644
--- a/sickbeard/notifiers/emby.py
+++ b/sickbeard/notifiers/emby.py
@@ -53,7 +53,7 @@ class EmbyNotifier(Notifier):
timeout=20, hooks=dict(response=self._cb_response), json=True)
return self.response and self.response.get('ok') and 200 == self.response.get('status_code') and \
- version <= map_list(lambda x: int(x), response.get('Version', '0.0.0.0').split('.'))
+ version <= map_list(lambda x: int(x), (response and response.get('Version') or '0.0.0.0').split('.'))
def update_library(self, show_obj=None, **kwargs):
""" Update library function
@@ -140,7 +140,7 @@ class EmbyNotifier(Notifier):
cs.setsockopt(SOL_SOCKET, SO_BROADCAST, 1)
cs.settimeout(10)
result, sock_issue = '', None
- for server in ('EmbyServer', 'MediaBrowserServer'):
+ for server in ('EmbyServer', 'MediaBrowserServer', 'JellyfinServer'):
bufr = 'who is %s?' % server
try:
assert len(bufr) == cs.sendto(bufr, ('255.255.255.255', mb_listen_port)), \
diff --git a/sickbeard/providers/filelist.py b/sickbeard/providers/filelist.py
index 6db79c7..2dbf720 100644
--- a/sickbeard/providers/filelist.py
+++ b/sickbeard/providers/filelist.py
@@ -1,111 +1,110 @@
-# coding=utf-8
-#
-# This file is part of SickGear.
-#
-# SickGear is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# SickGear is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with SickGear. If not, see .
-
-import re
-import traceback
-
-from . import generic
-from .. import logger
-from ..helpers import try_int
-from bs4_parser import BS4Parser
-
-from _23 import unidecode
-from six import iteritems
-
-
-class FLProvider(generic.TorrentProvider):
-
- def __init__(self):
- generic.TorrentProvider.__init__(self, 'FileList')
-
- self.url_base = 'https://filelist.ro/'
- self.urls = {'config_provider_home_uri': self.url_base,
- 'login_action': self.url_base + 'login.php',
- 'search': self.url_base + 'browse.php?search=%s&%s&incldead=0'}
-
- self.categories = {'Season': [14], 'Episode': [13, 21, 23], 'anime': [24]}
- self.categories['Cache'] = self.categories['Season'] + self.categories['Episode']
-
- self.url = self.urls['config_provider_home_uri']
-
- self.username, self.password, self.minseed, self.minleech = 4 * [None]
-
- def _authorised(self, **kwargs):
-
- return super(FLProvider, self)._authorised()
-
- def _search_provider(self, search_params, **kwargs):
-
- results = []
- if not self._authorised():
- return results
-
- items = {'Cache': [], 'Season': [], 'Episode': [], 'Propers': []}
-
- rc = dict([(k, re.compile('(?i)' + v)) for (k, v) in iteritems({'info': 'details', 'get': 'download'})])
- for mode in search_params:
- rc['cats'] = re.compile('(?i)cat=(?:%s)' % self._categories_string(mode, template='', delimiter='|'))
- for search_string in search_params[mode]:
- search_string = unidecode(search_string)
-
- html = self.get_url(self.urls['search'] % ('+'.join(search_string.split()),
- self._categories_string(mode, template='cats[]=%s')))
- if self.should_skip():
- return results
-
- cnt = len(items[mode])
- try:
- if not html or self._has_no_results(html):
- raise generic.HaltParseException
-
- with BS4Parser(html) as soup:
- tbl_rows = soup.find_all('div', 'torrentrow')
-
- if not len(tbl_rows):
- raise generic.HaltParseException
-
- for tr in tbl_rows:
- cells = tr.select('span[style*="cell"]')
- if 6 > len(cells):
- continue
- try:
- seeders, leechers, size = [try_int(n, n) for n in [
- cells[x].get_text().strip() for x in (-3, -2, -5)]]
- if not tr.find('a', href=rc['cats']) or self._reject_item(seeders, leechers):
- continue
-
- title = tr.find('a', href=rc['info']).get_text().strip()
- download_url = self._link(tr.find('a', href=rc['get'])['href'])
- except (AttributeError, TypeError, ValueError, IndexError):
- continue
-
- if title and download_url:
- items[mode].append((title, download_url, seeders, self._bytesizer(size)))
-
- except generic.HaltParseException:
- pass
- except (BaseException, Exception):
- logger.log(u'Failed to parse. Traceback: %s' % traceback.format_exc(), logger.ERROR)
-
- self._log_search(mode, len(items[mode]) - cnt, self.session.response.get('url'))
-
- results = self._sort_seeding(mode, results + items[mode])
-
- return results
-
-
-provider = FLProvider()
+# coding=utf-8
+#
+# This file is part of SickGear.
+#
+# SickGear is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# SickGear is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with SickGear. If not, see .
+
+import re
+import traceback
+
+from . import generic
+from .. import logger
+from ..helpers import try_int
+from bs4_parser import BS4Parser
+
+from _23 import unidecode
+from six import iteritems
+
+
+class FLProvider(generic.TorrentProvider):
+
+ def __init__(self):
+ generic.TorrentProvider.__init__(self, 'FileList')
+
+ self.url_base = 'https://filelist.io/'
+ self.urls = {'config_provider_home_uri': self.url_base,
+ 'login_action': self.url_base + 'login.php',
+ 'search': self.url_base + 'browse.php?search=%s&%s&incldead=0'}
+
+ self.categories = dict(shows=[13, 21, 23, 27], anime=[24])
+
+ self.url = self.urls['config_provider_home_uri']
+
+ self.username, self.password, self.minseed, self.minleech = 4 * [None]
+
+ def _authorised(self, **kwargs):
+
+ return super(FLProvider, self)._authorised()
+
+ def _search_provider(self, search_params, **kwargs):
+
+ results = []
+ if not self._authorised():
+ return results
+
+ items = {'Cache': [], 'Season': [], 'Episode': [], 'Propers': []}
+
+ rc = dict([(k, re.compile('(?i)' + v)) for (k, v) in iteritems({'info': 'details', 'get': 'download'})])
+ for mode in search_params:
+ rc['cats'] = re.compile('(?i)cat=(?:%s)' % self._categories_string(mode, template='', delimiter='|'))
+ for search_string in search_params[mode]:
+ search_string = unidecode(search_string)
+
+ html = self.get_url(self.urls['search'] % ('+'.join(search_string.split()),
+ self._categories_string(mode, template='cats[]=%s')))
+ if self.should_skip():
+ return results
+
+ cnt = len(items[mode])
+ try:
+ if not html or self._has_no_results(html):
+ raise generic.HaltParseException
+
+ with BS4Parser(html) as soup:
+ tbl_rows = soup.find_all('div', 'torrentrow')
+
+ if not len(tbl_rows):
+ raise generic.HaltParseException
+
+ for tr in tbl_rows:
+ cells = tr.select('span[style*="cell"]')
+ if 6 > len(cells):
+ continue
+ try:
+ seeders, leechers, size = [try_int(n, n) for n in [
+ cells[x].get_text().strip() for x in (-3, -2, -5)]]
+ if not tr.find('a', href=rc['cats']) or self._reject_item(seeders, leechers):
+ continue
+
+ title = tr.find('a', href=rc['info']).get_text().strip()
+ download_url = self._link(tr.find('a', href=rc['get'])['href'])
+ except (AttributeError, TypeError, ValueError, IndexError):
+ continue
+
+ if title and download_url:
+ items[mode].append((title, download_url, seeders, self._bytesizer(size)))
+
+ except generic.HaltParseException:
+ pass
+ except (BaseException, Exception):
+ logger.log(u'Failed to parse. Traceback: %s' % traceback.format_exc(), logger.ERROR)
+
+ self._log_search(mode, len(items[mode]) - cnt, self.session.response.get('url'))
+
+ results = self._sort_seeding(mode, results + items[mode])
+
+ return results
+
+
+provider = FLProvider()