episoder 1.0.0__tar.gz → 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- episoder-1.1.0/MANIFEST.in +2 -0
- {episoder-1.0.0 → episoder-1.1.0}/PKG-INFO +13 -8
- {episoder-1.0.0 → episoder-1.1.0}/README.md +7 -5
- episoder-1.1.0/episoder/cache.py +49 -0
- {episoder-1.0.0 → episoder-1.1.0}/episoder/config.py +1 -1
- {episoder-1.0.0 → episoder-1.1.0}/episoder/database.py +3 -3
- episoder-1.1.0/episoder/errors.py +9 -0
- {episoder-1.0.0 → episoder-1.1.0}/episoder/main.py +15 -12
- {episoder-1.0.0 → episoder-1.1.0}/episoder/sources/__init__.py +6 -3
- {episoder-1.0.0 → episoder-1.1.0}/episoder/sources/epguides.py +1 -1
- episoder-1.1.0/episoder/sources/thetvdb.py +20 -0
- episoder-1.0.0/episoder/sources/thetvdb.py → episoder-1.1.0/episoder/sources/thetvdb_legacy.py +32 -29
- episoder-1.1.0/episoder/sources/thetvdb_v4.py +325 -0
- {episoder-1.0.0 → episoder-1.1.0}/episoder/version.py +1 -1
- episoder-1.1.0/episoder.1 +260 -0
- {episoder-1.0.0 → episoder-1.1.0}/episoder.egg-info/PKG-INFO +13 -8
- {episoder-1.0.0 → episoder-1.1.0}/episoder.egg-info/SOURCES.txt +10 -2
- {episoder-1.0.0 → episoder-1.1.0}/episoder.egg-info/requires.txt +4 -1
- episoder-1.1.0/examples/home.episoder +30 -0
- {episoder-1.0.0 → episoder-1.1.0}/setup.cfg +5 -2
- episoder-1.1.0/test/test_cache.py +79 -0
- {episoder-1.0.0 → episoder-1.1.0}/test/test_config.py +1 -1
- {episoder-1.0.0 → episoder-1.1.0}/test/test_main.py +8 -15
- {episoder-1.0.0 → episoder-1.1.0}/test/test_sources.py +36 -9
- episoder-1.0.0/test/test_tvdb.py → episoder-1.1.0/test/test_tvdb_legacy.py +14 -13
- episoder-1.1.0/test/test_tvdb_v4.py +544 -0
- episoder-1.0.0/setup.py +0 -6
- {episoder-1.0.0 → episoder-1.1.0}/COPYING +0 -0
- {episoder-1.0.0 → episoder-1.1.0}/episoder/__init__.py +0 -0
- {episoder-1.0.0 → episoder-1.1.0}/episoder/episoder.py +0 -0
- {episoder-1.0.0 → episoder-1.1.0}/episoder/options.py +0 -0
- {episoder-1.0.0 → episoder-1.1.0}/episoder/output.py +0 -0
- {episoder-1.0.0 → episoder-1.1.0}/episoder/sources/parser.py +0 -0
- {episoder-1.0.0 → episoder-1.1.0}/episoder/sources/tvcom.py +0 -0
- {episoder-1.0.0 → episoder-1.1.0}/episoder.egg-info/dependency_links.txt +0 -0
- {episoder-1.0.0 → episoder-1.1.0}/episoder.egg-info/entry_points.txt +0 -0
- {episoder-1.0.0 → episoder-1.1.0}/episoder.egg-info/top_level.txt +0 -0
- {episoder-1.0.0 → episoder-1.1.0}/pyproject.toml +0 -0
- {episoder-1.0.0 → episoder-1.1.0}/test/test_commands.py +0 -0
- {episoder-1.0.0 → episoder-1.1.0}/test/test_database.py +0 -0
- {episoder-1.0.0 → episoder-1.1.0}/test/test_epguides_parser.py +0 -0
- {episoder-1.0.0 → episoder-1.1.0}/test/test_episode.py +0 -0
- {episoder-1.0.0 → episoder-1.1.0}/test/test_metainfo.py +0 -0
- {episoder-1.0.0 → episoder-1.1.0}/test/test_migration.py +0 -0
- {episoder-1.0.0 → episoder-1.1.0}/test/test_notify.py +0 -0
- {episoder-1.0.0 → episoder-1.1.0}/test/test_options.py +0 -0
- {episoder-1.0.0 → episoder-1.1.0}/test/test_output.py +0 -0
- {episoder-1.0.0 → episoder-1.1.0}/test/test_show.py +0 -0
- {episoder-1.0.0 → episoder-1.1.0}/test/test_tvcom.py +0 -0
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: episoder
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: TV episode notifier
|
|
5
5
|
Home-page: https://code.ott.net/episoder/
|
|
6
6
|
Author: Stefan Ott
|
|
7
7
|
Author-email: stefan@ott.net
|
|
8
|
+
License: GPL-3.0-or-later
|
|
8
9
|
Project-URL: Source, https://git.ott.net/episoder/
|
|
9
10
|
Classifier: Development Status :: 5 - Production/Stable
|
|
10
11
|
Classifier: Environment :: Console
|
|
11
12
|
Classifier: Intended Audience :: End Users/Desktop
|
|
12
|
-
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
13
13
|
Classifier: Natural Language :: English
|
|
14
14
|
Classifier: Operating System :: OS Independent
|
|
15
15
|
Classifier: Programming Language :: Python
|
|
@@ -18,8 +18,9 @@ Requires-Python: >=3.6
|
|
|
18
18
|
Description-Content-Type: text/markdown
|
|
19
19
|
License-File: COPYING
|
|
20
20
|
Requires-Dist: argparse
|
|
21
|
-
Requires-Dist: requests
|
|
22
21
|
Requires-Dist: beautifulsoup4
|
|
22
|
+
Requires-Dist: platformdirs
|
|
23
|
+
Requires-Dist: requests
|
|
23
24
|
Requires-Dist: sqlalchemy>=2.0
|
|
24
25
|
Provides-Extra: test
|
|
25
26
|
Requires-Dist: mypy; extra == "test"
|
|
@@ -28,7 +29,9 @@ Requires-Dist: pytest; extra == "test"
|
|
|
28
29
|
Requires-Dist: pyfakefs; extra == "test"
|
|
29
30
|
Requires-Dist: types-requests; extra == "test"
|
|
30
31
|
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: build; extra == "dev"
|
|
31
33
|
Requires-Dist: setuptools; extra == "dev"
|
|
34
|
+
Requires-Dist: twine; extra == "dev"
|
|
32
35
|
Dynamic: license-file
|
|
33
36
|
|
|
34
37
|
# episoder
|
|
@@ -55,8 +58,9 @@ The official website, bug tracker etc. for episoder can be found at
|
|
|
55
58
|
|
|
56
59
|
## Installation
|
|
57
60
|
|
|
58
|
-
You can install episoder by running `pip install episoder` or you can
|
|
59
|
-
the tarball from the official website
|
|
61
|
+
You can install episoder from PyPI by running `pip install episoder` or you can
|
|
62
|
+
download the tarball from the official website and install it manually using
|
|
63
|
+
`pip install episoder-<version>.tar.gz`.
|
|
60
64
|
|
|
61
65
|
## Getting started
|
|
62
66
|
|
|
@@ -88,9 +92,10 @@ the future.
|
|
|
88
92
|
|
|
89
93
|
## Configuration
|
|
90
94
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
95
|
+
Episoder does not need any configuration, it should run fine out of the box.
|
|
96
|
+
Still, some of its behaviour can be tweaked through a config file at
|
|
97
|
+
`~/.episoder`, the [man page](https://code.ott.net/episoder/doc/config.html) has
|
|
98
|
+
all the details.
|
|
94
99
|
|
|
95
100
|
## Using episoder
|
|
96
101
|
|
|
@@ -22,8 +22,9 @@ The official website, bug tracker etc. for episoder can be found at
|
|
|
22
22
|
|
|
23
23
|
## Installation
|
|
24
24
|
|
|
25
|
-
You can install episoder by running `pip install episoder` or you can
|
|
26
|
-
the tarball from the official website
|
|
25
|
+
You can install episoder from PyPI by running `pip install episoder` or you can
|
|
26
|
+
download the tarball from the official website and install it manually using
|
|
27
|
+
`pip install episoder-<version>.tar.gz`.
|
|
27
28
|
|
|
28
29
|
## Getting started
|
|
29
30
|
|
|
@@ -55,9 +56,10 @@ the future.
|
|
|
55
56
|
|
|
56
57
|
## Configuration
|
|
57
58
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
Episoder does not need any configuration, it should run fine out of the box.
|
|
60
|
+
Still, some of its behaviour can be tweaked through a config file at
|
|
61
|
+
`~/.episoder`, the [man page](https://code.ott.net/episoder/doc/config.html) has
|
|
62
|
+
all the details.
|
|
61
63
|
|
|
62
64
|
## Using episoder
|
|
63
65
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# episoder, https://code.ott.net/episoder
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2004-2026 Stefan Ott. All rights reserved.
|
|
4
|
+
#
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU General Public License
|
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
_FILE_NAME_TVDB_TOKEN = "tvdb_token"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Cache:
|
|
25
|
+
def __init__(self, path: str) -> None:
|
|
26
|
+
self._path = path
|
|
27
|
+
|
|
28
|
+
def set_tvdb_token(self, token: str) -> None:
|
|
29
|
+
if not os.path.exists(self._path):
|
|
30
|
+
os.mkdir(self._path)
|
|
31
|
+
|
|
32
|
+
fn = os.path.join(self._path, _FILE_NAME_TVDB_TOKEN)
|
|
33
|
+
with open(fn, 'w', encoding='utf8') as file:
|
|
34
|
+
file.write(token)
|
|
35
|
+
|
|
36
|
+
def get_tvdb_token(self) -> Optional[str]:
|
|
37
|
+
fn = os.path.join(self._path, _FILE_NAME_TVDB_TOKEN)
|
|
38
|
+
if not os.path.exists(fn):
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
with open(fn, 'r', encoding='utf8') as file:
|
|
42
|
+
token = file.read()
|
|
43
|
+
|
|
44
|
+
return token
|
|
45
|
+
|
|
46
|
+
def clear_tvdb_token(self) -> None:
|
|
47
|
+
fn = os.path.join(self._path, _FILE_NAME_TVDB_TOKEN)
|
|
48
|
+
if os.path.exists(fn):
|
|
49
|
+
os.unlink(fn)
|
|
@@ -35,7 +35,7 @@ class EpisoderConfig:
|
|
|
35
35
|
self.datafile = path.join(environ['HOME'], '.episodes')
|
|
36
36
|
self.dateformat = '%a, %b %d, %Y'
|
|
37
37
|
self.format = '%airdate %show %seasonx%epnum'
|
|
38
|
-
self.tvdb_key = '
|
|
38
|
+
self.tvdb_key = '1f4c52fc-410a-4868-9724-4c3eba19bdc7'
|
|
39
39
|
|
|
40
40
|
self.email_to: Optional[str] = None
|
|
41
41
|
self.email_username: Optional[str] = None
|
|
@@ -86,11 +86,11 @@ class Episode(Base):
|
|
|
86
86
|
prodnum: Optional[str], total: int) -> None:
|
|
87
87
|
super().__init__()
|
|
88
88
|
self.title = name
|
|
89
|
-
self.season =
|
|
90
|
-
self.episode =
|
|
89
|
+
self.season = season
|
|
90
|
+
self.episode = episode
|
|
91
91
|
self.airdate = aired
|
|
92
92
|
self.prodnum = prodnum
|
|
93
|
-
self.totalnum =
|
|
93
|
+
self.totalnum = total
|
|
94
94
|
|
|
95
95
|
def __str__(self) -> str:
|
|
96
96
|
return f"{self.show.name} {self.season}x{self.episode:02d}: " \
|
|
@@ -26,6 +26,9 @@ from datetime import date
|
|
|
26
26
|
from traceback import print_exc
|
|
27
27
|
from typing import Any, Callable, Optional
|
|
28
28
|
|
|
29
|
+
from platformdirs import PlatformDirs
|
|
30
|
+
|
|
31
|
+
from .cache import Cache
|
|
29
32
|
from .config import ConfigLoader, EpisoderConfig
|
|
30
33
|
from .database import Show
|
|
31
34
|
from .episoder import Database
|
|
@@ -33,7 +36,8 @@ from .options import OptionsLoader
|
|
|
33
36
|
from .output import ColorfulRenderer, ColorlessRenderer, EmailNotifier
|
|
34
37
|
from .output import ConsoleRenderer, NewEpisodesNotification, SmtpTestMessage
|
|
35
38
|
from .sources import parser_for, setup_sources
|
|
36
|
-
from .sources.
|
|
39
|
+
from .sources.thetvdb_legacy import TVDBLegacy
|
|
40
|
+
from .sources.thetvdb_v4 import TVDBv4
|
|
37
41
|
|
|
38
42
|
|
|
39
43
|
def configure_logging(args: Namespace) -> None:
|
|
@@ -220,15 +224,12 @@ class Episoder:
|
|
|
220
224
|
|
|
221
225
|
def search(self, keyword: str) -> None:
|
|
222
226
|
tvdb = parser_for('123') # numeric IDs are TVDB entries
|
|
223
|
-
assert isinstance(tvdb,
|
|
224
|
-
|
|
225
|
-
try:
|
|
226
|
-
print('ID\tName\n-------\t--------------------')
|
|
227
|
-
for show in tvdb.lookup(keyword):
|
|
228
|
-
print(f'{show.url}\t{show.name}')
|
|
227
|
+
assert isinstance(tvdb, (TVDBLegacy, TVDBv4))
|
|
228
|
+
shows = tvdb.lookup(keyword)
|
|
229
229
|
|
|
230
|
-
|
|
231
|
-
|
|
230
|
+
print('ID\tName\n-------\t--------------------')
|
|
231
|
+
for show in shows:
|
|
232
|
+
print(f'{show.url}\t{show.name}')
|
|
232
233
|
|
|
233
234
|
def _update_shows(self, shows: list[Show], after: Optional[date]) -> None:
|
|
234
235
|
assert self.database is not None
|
|
@@ -237,8 +238,7 @@ class Episoder:
|
|
|
237
238
|
parser = parser_for(show.url)
|
|
238
239
|
assert parser is not None
|
|
239
240
|
parser.parse(show, self.database)
|
|
240
|
-
# pylint: disable=broad-exception-caught
|
|
241
|
-
except Exception:
|
|
241
|
+
except Exception: # pylint: disable=broad-exception-caught
|
|
242
242
|
self._log.error('Error parsing %s', show)
|
|
243
243
|
print_exc()
|
|
244
244
|
self.database.rollback()
|
|
@@ -358,6 +358,9 @@ class CommandWrapper:
|
|
|
358
358
|
|
|
359
359
|
|
|
360
360
|
def main() -> None:
|
|
361
|
+
dirs = PlatformDirs('episoder', 'cockroach')
|
|
362
|
+
cache = Cache(dirs.user_cache_dir)
|
|
363
|
+
|
|
361
364
|
loader = OptionsLoader()
|
|
362
365
|
options = loader.parse_cmdline(sys.argv[1:])
|
|
363
366
|
loader.validate(options)
|
|
@@ -367,7 +370,7 @@ def main() -> None:
|
|
|
367
370
|
cfg = config_loader.load(options.c)
|
|
368
371
|
config_loader.validate(cfg)
|
|
369
372
|
|
|
370
|
-
setup_sources(cfg.agent, cfg.tvdb_key)
|
|
373
|
+
setup_sources(cfg.agent, cfg.tvdb_key, cache)
|
|
371
374
|
|
|
372
375
|
command = CommandWrapper(options, cfg)
|
|
373
376
|
command.run(options.func)
|
|
@@ -18,8 +18,10 @@
|
|
|
18
18
|
from typing import Optional, List
|
|
19
19
|
import requests
|
|
20
20
|
|
|
21
|
+
from episoder.cache import Cache
|
|
21
22
|
from .epguides import Epguides
|
|
22
|
-
from .
|
|
23
|
+
from .thetvdb_legacy import TVDBLegacy
|
|
24
|
+
from .thetvdb_v4 import TVDBv4
|
|
23
25
|
from .tvcom import TVCom
|
|
24
26
|
from .parser import Parser
|
|
25
27
|
|
|
@@ -28,13 +30,14 @@ session = requests.Session()
|
|
|
28
30
|
PARSERS: List[Parser] = []
|
|
29
31
|
|
|
30
32
|
|
|
31
|
-
def setup_sources(user_agent: str, tvdb_api_key: str) -> None:
|
|
33
|
+
def setup_sources(user_agent: str, tvdb_api_key: str, cache: Cache) -> None:
|
|
32
34
|
session.headers.update({'User-Agent': user_agent})
|
|
33
35
|
|
|
34
36
|
while len(PARSERS) > 0:
|
|
35
37
|
PARSERS.pop()
|
|
36
38
|
|
|
37
|
-
PARSERS.append(
|
|
39
|
+
PARSERS.append(TVDBv4(session, tvdb_api_key, cache))
|
|
40
|
+
PARSERS.append(TVDBLegacy(session, tvdb_api_key))
|
|
38
41
|
PARSERS.append(Epguides(session))
|
|
39
42
|
PARSERS.append(TVCom())
|
|
40
43
|
|
|
@@ -140,7 +140,7 @@ class Epguides(Parser):
|
|
|
140
140
|
then = self._get_date(day)
|
|
141
141
|
|
|
142
142
|
# prodnum is None, we don't have those
|
|
143
|
-
return Episode(title, season, epnum, then, None, total)
|
|
143
|
+
return Episode(title, int(season), int(epnum), then, None, total)
|
|
144
144
|
|
|
145
145
|
def _load_text_episode(self, line: str) -> Optional[Episode]:
|
|
146
146
|
# Known formatting supported by this fine regex:
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# episoder, https://code.ott.net/episoder
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2004-2026 Stefan Ott. All rights reserved.
|
|
4
|
+
#
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU General Public License
|
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Legacy keys use 16 digits
|
|
20
|
+
KEYLEN_LEGACY = 16
|
episoder-1.0.0/episoder/sources/thetvdb.py → episoder-1.1.0/episoder/sources/thetvdb_legacy.py
RENAMED
|
@@ -20,14 +20,19 @@ from json import dumps
|
|
|
20
20
|
from logging import getLogger
|
|
21
21
|
from datetime import datetime, date
|
|
22
22
|
from typing import Any, Dict, List, Tuple
|
|
23
|
+
from sys import stderr
|
|
23
24
|
|
|
24
25
|
from requests import Session
|
|
25
26
|
|
|
26
27
|
from episoder.database import Episode, Show
|
|
27
28
|
from episoder.episoder import Database
|
|
29
|
+
from episoder.errors import RemoteError, NotAuthenticated
|
|
28
30
|
from .parser import Parser
|
|
31
|
+
from .thetvdb import KEYLEN_LEGACY
|
|
29
32
|
|
|
30
33
|
|
|
34
|
+
# pylint: disable=duplicate-code
|
|
35
|
+
|
|
31
36
|
class TVDBState(ABC):
|
|
32
37
|
@abstractmethod
|
|
33
38
|
def login(self, key: str) -> None:
|
|
@@ -42,23 +47,10 @@ class TVDBState(ABC):
|
|
|
42
47
|
"""Load episodes with this parser"""
|
|
43
48
|
|
|
44
49
|
|
|
45
|
-
class
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
class TVDBShowNotFoundError(Exception):
|
|
50
|
-
pass
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
class TVDBNotLoggedInError(Exception):
|
|
54
|
-
pass
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
class TVDB(Parser):
|
|
58
|
-
"""Parser for episodes from thetvdb.com"""
|
|
50
|
+
class TVDBLegacy(Parser):
|
|
51
|
+
"""Parser for episodes from thetvdb.com legacy API"""
|
|
59
52
|
|
|
60
53
|
def __init__(self, session: Session, api_key: str) -> None:
|
|
61
|
-
self._session = session
|
|
62
54
|
self._api_key = api_key
|
|
63
55
|
self._state: TVDBState = TVDBOffline(self, session)
|
|
64
56
|
|
|
@@ -66,7 +58,7 @@ class TVDB(Parser):
|
|
|
66
58
|
return str(self._state)
|
|
67
59
|
|
|
68
60
|
def __repr__(self) -> str:
|
|
69
|
-
return f'
|
|
61
|
+
return f'TVDBLegacy {repr(self._state)}'
|
|
70
62
|
|
|
71
63
|
def lookup(self, text: str) -> List[Show]:
|
|
72
64
|
self._state.login(self._api_key)
|
|
@@ -80,13 +72,13 @@ class TVDB(Parser):
|
|
|
80
72
|
self._state = state
|
|
81
73
|
|
|
82
74
|
def accept(self, url: str) -> bool:
|
|
83
|
-
return url.isdigit()
|
|
75
|
+
return url.isdigit() and len(self._api_key) <= KEYLEN_LEGACY
|
|
84
76
|
|
|
85
77
|
|
|
86
78
|
class TVDBOffline(TVDBState):
|
|
87
|
-
def __init__(self, tvdb:
|
|
79
|
+
def __init__(self, tvdb: TVDBLegacy, session: Session) -> None:
|
|
88
80
|
self._tvdb = tvdb
|
|
89
|
-
self._log = getLogger('
|
|
81
|
+
self._log = getLogger('TVDBLegacy (offline)')
|
|
90
82
|
self._session = session
|
|
91
83
|
|
|
92
84
|
def __str__(self) -> str:
|
|
@@ -102,28 +94,36 @@ class TVDBOffline(TVDBState):
|
|
|
102
94
|
response = self._session.post(url, body, headers=head, timeout=10)
|
|
103
95
|
data = response.json()
|
|
104
96
|
|
|
105
|
-
if response.status_code
|
|
106
|
-
raise
|
|
97
|
+
if response.status_code != 200:
|
|
98
|
+
raise RemoteError(data.get('Error'))
|
|
107
99
|
|
|
108
100
|
self._log.info('Successful login')
|
|
109
101
|
return data['token']
|
|
110
102
|
|
|
111
103
|
def lookup(self, _: str) -> List[Show]:
|
|
112
|
-
raise
|
|
104
|
+
raise NotAuthenticated()
|
|
113
105
|
|
|
114
106
|
def login(self, key: str) -> None:
|
|
107
|
+
print("""** WARNING **
|
|
108
|
+
You are using the legacy API for the TVDB. This API is deprecated and
|
|
109
|
+
frequently broken.
|
|
110
|
+
|
|
111
|
+
Please create a new API key for episoder on https://www.thetvdb.com/
|
|
112
|
+
and update your configuration.
|
|
113
|
+
""", file=stderr)
|
|
114
|
+
|
|
115
115
|
body = {'apikey': key}
|
|
116
116
|
token = self._post_login(body)
|
|
117
117
|
self._tvdb.change(TVDBOnline(token, self._session))
|
|
118
118
|
|
|
119
119
|
def parse(self, _: Show, __: Database) -> None:
|
|
120
|
-
raise
|
|
120
|
+
raise NotAuthenticated()
|
|
121
121
|
|
|
122
122
|
|
|
123
123
|
class TVDBOnline(TVDBState):
|
|
124
124
|
def __init__(self, token: str, session: Session) -> None:
|
|
125
125
|
self._token = token
|
|
126
|
-
self._log = getLogger('
|
|
126
|
+
self._log = getLogger('TVDBLegacy (online)')
|
|
127
127
|
self._session = session
|
|
128
128
|
|
|
129
129
|
def __str__(self) -> str:
|
|
@@ -142,7 +142,7 @@ class TVDBOnline(TVDBState):
|
|
|
142
142
|
data: Dict[str, Any] = response.json()
|
|
143
143
|
|
|
144
144
|
if response.status_code == 404:
|
|
145
|
-
raise
|
|
145
|
+
raise RemoteError(data.get('Error'))
|
|
146
146
|
|
|
147
147
|
return data
|
|
148
148
|
|
|
@@ -160,8 +160,11 @@ class TVDBOnline(TVDBState):
|
|
|
160
160
|
url = str(entry.get('id')).encode('utf8').decode('utf8')
|
|
161
161
|
return Show(name, url=url)
|
|
162
162
|
|
|
163
|
-
|
|
164
|
-
|
|
163
|
+
try:
|
|
164
|
+
matches = self._get('search/series', {'name': text})
|
|
165
|
+
return list(map(mkshow, matches.get('data', {})))
|
|
166
|
+
except RemoteError:
|
|
167
|
+
return []
|
|
165
168
|
|
|
166
169
|
def login(self, _: str) -> None:
|
|
167
170
|
pass
|
|
@@ -197,7 +200,7 @@ class TVDBOnline(TVDBState):
|
|
|
197
200
|
try:
|
|
198
201
|
more = self._fetch_episodes(show, next_)
|
|
199
202
|
episodes.extend(more)
|
|
200
|
-
except
|
|
203
|
+
except RemoteError:
|
|
201
204
|
msg = "Error parsing %s: failed to load page %d"
|
|
202
205
|
self._log.error(msg, show.name, next_)
|
|
203
206
|
|
|
@@ -221,7 +224,7 @@ class TVDBOnline(TVDBState):
|
|
|
221
224
|
# load episodes
|
|
222
225
|
try:
|
|
223
226
|
episodes = self._fetch_episodes(show, 1)
|
|
224
|
-
except
|
|
227
|
+
except RemoteError:
|
|
225
228
|
return
|
|
226
229
|
|
|
227
230
|
for (idx, episode) in enumerate(sorted(episodes)):
|