plexflow 0.0.64__py3-none-any.whl
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.
- plexflow/__init__.py +0 -0
- plexflow/__main__.py +15 -0
- plexflow/core/.DS_Store +0 -0
- plexflow/core/__init__.py +0 -0
- plexflow/core/context/__init__.py +0 -0
- plexflow/core/context/metadata/__init__.py +0 -0
- plexflow/core/context/metadata/context.py +32 -0
- plexflow/core/context/metadata/tmdb/__init__.py +0 -0
- plexflow/core/context/metadata/tmdb/context.py +45 -0
- plexflow/core/context/partial_context.py +46 -0
- plexflow/core/context/partials/__init__.py +8 -0
- plexflow/core/context/partials/cache.py +16 -0
- plexflow/core/context/partials/context.py +12 -0
- plexflow/core/context/partials/ids.py +37 -0
- plexflow/core/context/partials/movie.py +115 -0
- plexflow/core/context/partials/tgx_batch.py +33 -0
- plexflow/core/context/partials/tgx_context.py +34 -0
- plexflow/core/context/partials/torrents.py +23 -0
- plexflow/core/context/partials/watchlist.py +35 -0
- plexflow/core/context/plexflow_context.py +29 -0
- plexflow/core/context/plexflow_property.py +36 -0
- plexflow/core/context/root/__init__.py +0 -0
- plexflow/core/context/root/context.py +25 -0
- plexflow/core/context/select/__init__.py +0 -0
- plexflow/core/context/select/context.py +45 -0
- plexflow/core/context/torrent/__init__.py +0 -0
- plexflow/core/context/torrent/context.py +43 -0
- plexflow/core/context/torrent/tpb/__init__.py +0 -0
- plexflow/core/context/torrent/tpb/context.py +45 -0
- plexflow/core/context/torrent/yts/__init__.py +0 -0
- plexflow/core/context/torrent/yts/context.py +45 -0
- plexflow/core/context/watchlist/__init__.py +0 -0
- plexflow/core/context/watchlist/context.py +46 -0
- plexflow/core/downloads/__init__.py +0 -0
- plexflow/core/downloads/candidates/__init__.py +0 -0
- plexflow/core/downloads/candidates/download_candidate.py +210 -0
- plexflow/core/downloads/candidates/filtered.py +51 -0
- plexflow/core/downloads/candidates/utils.py +39 -0
- plexflow/core/env/__init__.py +0 -0
- plexflow/core/env/env.py +31 -0
- plexflow/core/genai/__init__.py +0 -0
- plexflow/core/genai/bot.py +9 -0
- plexflow/core/genai/plexa.py +54 -0
- plexflow/core/genai/torrent/imdb_verify.py +65 -0
- plexflow/core/genai/torrent/movie.py +25 -0
- plexflow/core/genai/utils/__init__.py +0 -0
- plexflow/core/genai/utils/loader.py +5 -0
- plexflow/core/metadata/__init__.py +0 -0
- plexflow/core/metadata/auto/__init__.py +0 -0
- plexflow/core/metadata/auto/auto_meta.py +40 -0
- plexflow/core/metadata/auto/auto_providers/__init__.py +0 -0
- plexflow/core/metadata/auto/auto_providers/auto/__init__.py +0 -0
- plexflow/core/metadata/auto/auto_providers/auto/episode.py +49 -0
- plexflow/core/metadata/auto/auto_providers/auto/item.py +55 -0
- plexflow/core/metadata/auto/auto_providers/auto/movie.py +13 -0
- plexflow/core/metadata/auto/auto_providers/auto/season.py +43 -0
- plexflow/core/metadata/auto/auto_providers/auto/show.py +26 -0
- plexflow/core/metadata/auto/auto_providers/imdb/__init__.py +0 -0
- plexflow/core/metadata/auto/auto_providers/imdb/movie.py +36 -0
- plexflow/core/metadata/auto/auto_providers/imdb/show.py +45 -0
- plexflow/core/metadata/auto/auto_providers/moviemeter/__init__.py +0 -0
- plexflow/core/metadata/auto/auto_providers/moviemeter/movie.py +40 -0
- plexflow/core/metadata/auto/auto_providers/plex/__init__.py +0 -0
- plexflow/core/metadata/auto/auto_providers/plex/movie.py +39 -0
- plexflow/core/metadata/auto/auto_providers/tmdb/__init__.py +0 -0
- plexflow/core/metadata/auto/auto_providers/tmdb/episode.py +30 -0
- plexflow/core/metadata/auto/auto_providers/tmdb/movie.py +36 -0
- plexflow/core/metadata/auto/auto_providers/tmdb/season.py +23 -0
- plexflow/core/metadata/auto/auto_providers/tmdb/show.py +41 -0
- plexflow/core/metadata/auto/auto_providers/tmdb.py +92 -0
- plexflow/core/metadata/auto/auto_providers/tvdb/__init__.py +0 -0
- plexflow/core/metadata/auto/auto_providers/tvdb/episode.py +28 -0
- plexflow/core/metadata/auto/auto_providers/tvdb/movie.py +36 -0
- plexflow/core/metadata/auto/auto_providers/tvdb/season.py +25 -0
- plexflow/core/metadata/auto/auto_providers/tvdb/show.py +41 -0
- plexflow/core/metadata/providers/__init__.py +0 -0
- plexflow/core/metadata/providers/imdb/__init__.py +0 -0
- plexflow/core/metadata/providers/imdb/datatypes.py +53 -0
- plexflow/core/metadata/providers/imdb/imdb.py +112 -0
- plexflow/core/metadata/providers/moviemeter/__init__.py +0 -0
- plexflow/core/metadata/providers/moviemeter/datatypes.py +111 -0
- plexflow/core/metadata/providers/moviemeter/moviemeter.py +42 -0
- plexflow/core/metadata/providers/plex/__init__.py +0 -0
- plexflow/core/metadata/providers/plex/datatypes.py +693 -0
- plexflow/core/metadata/providers/plex/plex.py +167 -0
- plexflow/core/metadata/providers/tmdb/__init__.py +0 -0
- plexflow/core/metadata/providers/tmdb/datatypes.py +460 -0
- plexflow/core/metadata/providers/tmdb/tmdb.py +85 -0
- plexflow/core/metadata/providers/tvdb/__init__.py +0 -0
- plexflow/core/metadata/providers/tvdb/datatypes.py +257 -0
- plexflow/core/metadata/providers/tvdb/tv_datatypes.py +554 -0
- plexflow/core/metadata/providers/tvdb/tvdb.py +65 -0
- plexflow/core/metadata/providers/universal/__init__.py +0 -0
- plexflow/core/metadata/providers/universal/movie.py +130 -0
- plexflow/core/metadata/providers/universal/old.py +192 -0
- plexflow/core/metadata/providers/universal/show.py +107 -0
- plexflow/core/plex/__init__.py +0 -0
- plexflow/core/plex/api/context/authorized.py +15 -0
- plexflow/core/plex/api/context/discover.py +14 -0
- plexflow/core/plex/api/context/library.py +14 -0
- plexflow/core/plex/discover/__init__.py +0 -0
- plexflow/core/plex/discover/activity.py +448 -0
- plexflow/core/plex/discover/comment.py +89 -0
- plexflow/core/plex/discover/feed.py +11 -0
- plexflow/core/plex/hooks/__init__.py +0 -0
- plexflow/core/plex/hooks/plex_authorized.py +60 -0
- plexflow/core/plex/hooks/plexflow_database.py +6 -0
- plexflow/core/plex/library/__init__.py +0 -0
- plexflow/core/plex/library/library.py +103 -0
- plexflow/core/plex/token/__init__.py +0 -0
- plexflow/core/plex/token/auto_token.py +91 -0
- plexflow/core/plex/utils/__init__.py +0 -0
- plexflow/core/plex/utils/paginated.py +39 -0
- plexflow/core/plex/watchlist/__init__.py +0 -0
- plexflow/core/plex/watchlist/datatypes.py +124 -0
- plexflow/core/plex/watchlist/watchlist.py +23 -0
- plexflow/core/storage/__init__.py +0 -0
- plexflow/core/storage/object/__init__.py +0 -0
- plexflow/core/storage/object/plexflow_storage.py +143 -0
- plexflow/core/storage/object/redis_storage.py +169 -0
- plexflow/core/subtitles/__init__.py +0 -0
- plexflow/core/subtitles/providers/__init__.py +0 -0
- plexflow/core/subtitles/providers/auto_subtitles.py +48 -0
- plexflow/core/subtitles/providers/oss/__init__.py +0 -0
- plexflow/core/subtitles/providers/oss/datatypes.py +104 -0
- plexflow/core/subtitles/providers/oss/download.py +48 -0
- plexflow/core/subtitles/providers/oss/old.py +144 -0
- plexflow/core/subtitles/providers/oss/oss.py +400 -0
- plexflow/core/subtitles/providers/oss/oss_subtitle.py +32 -0
- plexflow/core/subtitles/providers/oss/search.py +52 -0
- plexflow/core/subtitles/providers/oss/unlimited_oss.py +231 -0
- plexflow/core/subtitles/providers/oss/utils/__init__.py +0 -0
- plexflow/core/subtitles/providers/oss/utils/config.py +63 -0
- plexflow/core/subtitles/providers/oss/utils/download_client.py +22 -0
- plexflow/core/subtitles/providers/oss/utils/exceptions.py +35 -0
- plexflow/core/subtitles/providers/oss/utils/file_utils.py +83 -0
- plexflow/core/subtitles/providers/oss/utils/languages.py +78 -0
- plexflow/core/subtitles/providers/oss/utils/response_base.py +221 -0
- plexflow/core/subtitles/providers/oss/utils/responses.py +176 -0
- plexflow/core/subtitles/providers/oss/utils/srt.py +561 -0
- plexflow/core/subtitles/results/__init__.py +0 -0
- plexflow/core/subtitles/results/subtitle.py +170 -0
- plexflow/core/torrents/__init__.py +0 -0
- plexflow/core/torrents/analyzers/analyzed_torrent.py +143 -0
- plexflow/core/torrents/analyzers/analyzer.py +45 -0
- plexflow/core/torrents/analyzers/torrentquest/analyzer.py +47 -0
- plexflow/core/torrents/auto/auto_providers/auto/__init__.py +0 -0
- plexflow/core/torrents/auto/auto_providers/auto/torrent.py +64 -0
- plexflow/core/torrents/auto/auto_providers/tpb/torrent.py +62 -0
- plexflow/core/torrents/auto/auto_torrents.py +29 -0
- plexflow/core/torrents/providers/__init__.py +0 -0
- plexflow/core/torrents/providers/ext/__init__.py +0 -0
- plexflow/core/torrents/providers/ext/ext.py +18 -0
- plexflow/core/torrents/providers/ext/utils.py +64 -0
- plexflow/core/torrents/providers/extratorrent/__init__.py +0 -0
- plexflow/core/torrents/providers/extratorrent/extratorrent.py +21 -0
- plexflow/core/torrents/providers/extratorrent/utils.py +66 -0
- plexflow/core/torrents/providers/eztv/__init__.py +0 -0
- plexflow/core/torrents/providers/eztv/eztv.py +47 -0
- plexflow/core/torrents/providers/eztv/utils.py +83 -0
- plexflow/core/torrents/providers/rarbg2/__init__.py +0 -0
- plexflow/core/torrents/providers/rarbg2/rarbg2.py +19 -0
- plexflow/core/torrents/providers/rarbg2/utils.py +76 -0
- plexflow/core/torrents/providers/snowfl/__init__.py +0 -0
- plexflow/core/torrents/providers/snowfl/snowfl.py +36 -0
- plexflow/core/torrents/providers/snowfl/utils.py +59 -0
- plexflow/core/torrents/providers/tgx/__init__.py +0 -0
- plexflow/core/torrents/providers/tgx/context.py +50 -0
- plexflow/core/torrents/providers/tgx/dump.py +40 -0
- plexflow/core/torrents/providers/tgx/tgx.py +22 -0
- plexflow/core/torrents/providers/tgx/utils.py +61 -0
- plexflow/core/torrents/providers/therarbg/__init__.py +0 -0
- plexflow/core/torrents/providers/therarbg/therarbg.py +17 -0
- plexflow/core/torrents/providers/therarbg/utils.py +61 -0
- plexflow/core/torrents/providers/torrentquest/__init__.py +0 -0
- plexflow/core/torrents/providers/torrentquest/torrentquest.py +20 -0
- plexflow/core/torrents/providers/torrentquest/utils.py +70 -0
- plexflow/core/torrents/providers/tpb/__init__.py +0 -0
- plexflow/core/torrents/providers/tpb/tpb.py +17 -0
- plexflow/core/torrents/providers/tpb/utils.py +139 -0
- plexflow/core/torrents/providers/yts/__init__.py +0 -0
- plexflow/core/torrents/providers/yts/utils.py +57 -0
- plexflow/core/torrents/providers/yts/yts.py +31 -0
- plexflow/core/torrents/results/__init__.py +0 -0
- plexflow/core/torrents/results/torrent.py +165 -0
- plexflow/core/torrents/results/universal.py +220 -0
- plexflow/core/torrents/results/utils.py +15 -0
- plexflow/events/__init__.py +0 -0
- plexflow/events/download/__init__.py +0 -0
- plexflow/events/download/torrent_events.py +96 -0
- plexflow/events/publish/__init__.py +0 -0
- plexflow/events/publish/publish.py +34 -0
- plexflow/logging/__init__.py +0 -0
- plexflow/logging/log_setup.py +8 -0
- plexflow/spiders/quiet_logger.py +9 -0
- plexflow/spiders/tgx/pipelines/dump_json_pipeline.py +30 -0
- plexflow/spiders/tgx/pipelines/meta_pipeline.py +13 -0
- plexflow/spiders/tgx/pipelines/publish_pipeline.py +14 -0
- plexflow/spiders/tgx/pipelines/torrent_info_pipeline.py +12 -0
- plexflow/spiders/tgx/pipelines/validation_pipeline.py +17 -0
- plexflow/spiders/tgx/settings.py +36 -0
- plexflow/spiders/tgx/spider.py +72 -0
- plexflow/utils/__init__.py +0 -0
- plexflow/utils/antibot/human_like_requests.py +122 -0
- plexflow/utils/api/__init__.py +0 -0
- plexflow/utils/api/context/http.py +62 -0
- plexflow/utils/api/rest/__init__.py +0 -0
- plexflow/utils/api/rest/antibot_restful.py +68 -0
- plexflow/utils/api/rest/restful.py +49 -0
- plexflow/utils/captcha/__init__.py +0 -0
- plexflow/utils/captcha/bypass/__init__.py +0 -0
- plexflow/utils/captcha/bypass/decode_audio.py +34 -0
- plexflow/utils/download/__init__.py +0 -0
- plexflow/utils/download/gz.py +26 -0
- plexflow/utils/filesystem/__init__.py +0 -0
- plexflow/utils/filesystem/search.py +129 -0
- plexflow/utils/gmail/__init__.py +0 -0
- plexflow/utils/gmail/mails.py +116 -0
- plexflow/utils/hooks/__init__.py +0 -0
- plexflow/utils/hooks/http.py +84 -0
- plexflow/utils/hooks/postgresql.py +93 -0
- plexflow/utils/hooks/redis.py +112 -0
- plexflow/utils/image/storage.py +36 -0
- plexflow/utils/imdb/__init__.py +0 -0
- plexflow/utils/imdb/imdb_codes.py +107 -0
- plexflow/utils/pubsub/consume.py +82 -0
- plexflow/utils/pubsub/produce.py +25 -0
- plexflow/utils/retry/__init__.py +0 -0
- plexflow/utils/retry/utils.py +38 -0
- plexflow/utils/strings/__init__.py +0 -0
- plexflow/utils/strings/filesize.py +55 -0
- plexflow/utils/strings/language.py +14 -0
- plexflow/utils/subtitle/search.py +76 -0
- plexflow/utils/tasks/decorators.py +78 -0
- plexflow/utils/tasks/k8s/task.py +70 -0
- plexflow/utils/thread_safe/safe_list.py +54 -0
- plexflow/utils/thread_safe/safe_set.py +69 -0
- plexflow/utils/torrent/__init__.py +0 -0
- plexflow/utils/torrent/analyze.py +118 -0
- plexflow/utils/torrent/extract/common.py +37 -0
- plexflow/utils/torrent/extract/ext.py +2391 -0
- plexflow/utils/torrent/extract/extratorrent.py +56 -0
- plexflow/utils/torrent/extract/kat.py +1581 -0
- plexflow/utils/torrent/extract/tgx.py +96 -0
- plexflow/utils/torrent/extract/therarbg.py +170 -0
- plexflow/utils/torrent/extract/torrentquest.py +171 -0
- plexflow/utils/torrent/files.py +36 -0
- plexflow/utils/torrent/hash.py +90 -0
- plexflow/utils/transcribe/__init__.py +0 -0
- plexflow/utils/transcribe/speech2text.py +40 -0
- plexflow/utils/video/__init__.py +0 -0
- plexflow/utils/video/subtitle.py +73 -0
- plexflow-0.0.64.dist-info/METADATA +71 -0
- plexflow-0.0.64.dist-info/RECORD +256 -0
- plexflow-0.0.64.dist-info/WHEEL +4 -0
- plexflow-0.0.64.dist-info/entry_points.txt +24 -0
@@ -0,0 +1,167 @@
|
|
1
|
+
from typing import Union, List
|
2
|
+
from plexflow.core.plex.hooks.plex_authorized import PlexAuthorizedHttpHook
|
3
|
+
from plexflow.core.plex.api.context.discover import PlexDiscoverRequestContext
|
4
|
+
from plexflow.core.metadata.providers.plex.datatypes import MovieMediaContainer, PlexMovieMetadata, ShowMediaContainer, PlexShowMetadata, PlexSeasonMetadata, SeasonMediaContainer, PlexEpisodeMetadata, EpisodeMediaContainer, PlexUserState, WatchStateMediaContainer
|
5
|
+
|
6
|
+
def search_movie_by_rating_key(key: str) -> Union[PlexMovieMetadata, None]:
|
7
|
+
"""
|
8
|
+
Search for a movie using its rating key.
|
9
|
+
|
10
|
+
Parameters:
|
11
|
+
key (str): The rating key of the movie.
|
12
|
+
|
13
|
+
Returns:
|
14
|
+
Union[PlexMovieMetadata, None]: A PlexMovieMetadata object containing all the details of the movie.
|
15
|
+
None: If the movie is not found or an error occurs.
|
16
|
+
|
17
|
+
Example:
|
18
|
+
>>> search_movie_by_rating_key('12345')
|
19
|
+
PlexMovieMetadata(title='The Shawshank Redemption', ratingKey='12345', release_date=datetime.datetime(1994, 9, 23, 0, 0), runtime=8520, rating=9.3, votes=2400000, rank=1)
|
20
|
+
"""
|
21
|
+
context = PlexDiscoverRequestContext()
|
22
|
+
response = context.get(f"/library/metadata/{key}")
|
23
|
+
|
24
|
+
response.raise_for_status()
|
25
|
+
|
26
|
+
data = response.json()
|
27
|
+
|
28
|
+
# import pprint as pp
|
29
|
+
# pp.pprint(data.get("MediaContainer"))
|
30
|
+
container: MovieMediaContainer = MovieMediaContainer.from_dict(data.get("MediaContainer"))
|
31
|
+
|
32
|
+
if len(container.Metadata) > 0:
|
33
|
+
return container.Metadata[0]
|
34
|
+
|
35
|
+
def search_show_by_rating_key(key: str) -> Union[PlexShowMetadata, None]:
|
36
|
+
"""
|
37
|
+
Search for a show using its rating key.
|
38
|
+
|
39
|
+
Parameters:
|
40
|
+
key (str): The rating key of the show.
|
41
|
+
|
42
|
+
Returns:
|
43
|
+
Union[PlexShowMetadata, None]: A PlexShowMetadata object containing all the details of the show.
|
44
|
+
None: If the show is not found or an error occurs.
|
45
|
+
|
46
|
+
Example:
|
47
|
+
>>> search_show_by_rating_key('12345')
|
48
|
+
PlexShowMetadata(title='Friends', ratingKey='12345', parentRatingKey='67890')
|
49
|
+
"""
|
50
|
+
context = PlexDiscoverRequestContext()
|
51
|
+
response = context.get(f"/library/metadata/{key}")
|
52
|
+
|
53
|
+
response.raise_for_status()
|
54
|
+
|
55
|
+
data = response.json()
|
56
|
+
|
57
|
+
container: ShowMediaContainer = ShowMediaContainer.from_dict(data.get("MediaContainer"))
|
58
|
+
|
59
|
+
if len(container.Metadata) > 0:
|
60
|
+
return container.Metadata[0]
|
61
|
+
|
62
|
+
|
63
|
+
def search_seasons_by_show_rating_key(key: str) -> Union[List[PlexSeasonMetadata], None]:
|
64
|
+
"""
|
65
|
+
Search for seasons of a show using its rating key.
|
66
|
+
|
67
|
+
Parameters:
|
68
|
+
key (str): The rating key of the show.
|
69
|
+
|
70
|
+
Returns:
|
71
|
+
List[PlexSeasonMetadata]: A list of PlexSeasonMetadata objects containing the details of the seasons.
|
72
|
+
None: If the show is not found or an error occurs.
|
73
|
+
|
74
|
+
Example:
|
75
|
+
>>> search_seasons_by_show_rating_key('12345')
|
76
|
+
[PlexSeasonMetadata(title='Season 1', ratingKey='123', parentRatingKey='456'), PlexSeasonMetadata(title='Season 2', ratingKey='789', parentRatingKey='456')]
|
77
|
+
"""
|
78
|
+
context = PlexDiscoverRequestContext()
|
79
|
+
response = context.get(f"/library/metadata/{key}/children")
|
80
|
+
|
81
|
+
response.raise_for_status()
|
82
|
+
|
83
|
+
data = response.json()
|
84
|
+
|
85
|
+
container: SeasonMediaContainer = SeasonMediaContainer.from_dict(data.get("MediaContainer"))
|
86
|
+
|
87
|
+
if len(container.Metadata) > 0:
|
88
|
+
return container.Metadata
|
89
|
+
|
90
|
+
|
91
|
+
def search_episodes_by_season_rating_key(key: str) -> Union[List[PlexEpisodeMetadata], None]:
|
92
|
+
"""
|
93
|
+
Search for episodes of a season using its rating key.
|
94
|
+
|
95
|
+
Parameters:
|
96
|
+
key (str): The rating key of the season.
|
97
|
+
|
98
|
+
Returns:
|
99
|
+
List[PlexEpisodeMetadata]: A list of PlexEpisodeMetadata objects containing the details of the episodes.
|
100
|
+
None: If the season is not found or an error occurs.
|
101
|
+
|
102
|
+
Example:
|
103
|
+
>>> search_episodes_by_season_rating_key('12345')
|
104
|
+
[PlexEpisodeMetadata(title='Episode 1', duration=3600, rating=8.5), PlexEpisodeMetadata(title='Episode 2', duration=3600, rating=9.0)]
|
105
|
+
"""
|
106
|
+
context = PlexDiscoverRequestContext()
|
107
|
+
response = context.get(f"/library/metadata/{key}/children")
|
108
|
+
|
109
|
+
response.raise_for_status()
|
110
|
+
|
111
|
+
data = response.json()
|
112
|
+
|
113
|
+
container: EpisodeMediaContainer = EpisodeMediaContainer.from_dict(data.get("MediaContainer"))
|
114
|
+
|
115
|
+
if len(container.Metadata) > 0:
|
116
|
+
return container.Metadata
|
117
|
+
|
118
|
+
def get_all_episodes_by_show_rating_key(key: str) -> List[PlexEpisodeMetadata]:
|
119
|
+
"""
|
120
|
+
Get all episodes of a show using its rating key.
|
121
|
+
|
122
|
+
Parameters:
|
123
|
+
key (str): The rating key of the show.
|
124
|
+
|
125
|
+
Returns:
|
126
|
+
List[PlexEpisodeMetadata]: A list of PlexEpisodeMetadata objects containing the details of the episodes.
|
127
|
+
|
128
|
+
Example:
|
129
|
+
>>> get_all_episodes_by_show_rating_key('12345')
|
130
|
+
[PlexEpisodeMetadata(title='Episode 1', duration=3600, rating=8.5), PlexEpisodeMetadata(title='Episode 2', duration=3600, rating=9.0)]
|
131
|
+
"""
|
132
|
+
show = search_show_by_rating_key(key=key)
|
133
|
+
|
134
|
+
seasons = search_seasons_by_show_rating_key(show.ratingKey)
|
135
|
+
|
136
|
+
episodes = []
|
137
|
+
for season in seasons:
|
138
|
+
tmp = search_episodes_by_season_rating_key(season.ratingKey)
|
139
|
+
for x in tmp:
|
140
|
+
episodes.append(x)
|
141
|
+
return episodes
|
142
|
+
|
143
|
+
def get_watch_state_by_rating_key(key: str) -> Union[PlexUserState, None]:
|
144
|
+
"""
|
145
|
+
Get the watch state of a movie or show using its rating key.
|
146
|
+
|
147
|
+
Parameters:
|
148
|
+
key (str): The rating key of the movie or show.
|
149
|
+
|
150
|
+
Returns:
|
151
|
+
PlexUserState: A PlexUserState object containing the watch state.
|
152
|
+
None: If the movie or show is not found or an error occurs.
|
153
|
+
|
154
|
+
Example:
|
155
|
+
>>> get_watch_state_by_rating_key('12345')
|
156
|
+
PlexUserState(state='watched', last_viewed_at=datetime.datetime(2022, 1, 1, 0, 0))
|
157
|
+
"""
|
158
|
+
context = PlexDiscoverRequestContext()
|
159
|
+
response = context.get(f"/library/metadata/{key}/userState")
|
160
|
+
|
161
|
+
response.raise_for_status()
|
162
|
+
|
163
|
+
data = response.json()
|
164
|
+
|
165
|
+
container: WatchStateMediaContainer = WatchStateMediaContainer.from_dict(data.get("MediaContainer"))
|
166
|
+
|
167
|
+
return container.UserState
|
File without changes
|
@@ -0,0 +1,460 @@
|
|
1
|
+
from dataclasses import dataclass, field
|
2
|
+
from typing import List, Optional, Dict
|
3
|
+
from dataclasses_json import dataclass_json, Undefined
|
4
|
+
import json
|
5
|
+
|
6
|
+
@dataclass_json(undefined=Undefined.EXCLUDE)
|
7
|
+
@dataclass
|
8
|
+
class Genre:
|
9
|
+
"""
|
10
|
+
Represents a genre of a movie.
|
11
|
+
|
12
|
+
Attributes:
|
13
|
+
id (int): The unique identifier of the genre.
|
14
|
+
name (str): The name of the genre.
|
15
|
+
"""
|
16
|
+
id: Optional[int] = None
|
17
|
+
name: Optional[str] = None
|
18
|
+
_catchall: Dict[str, str] = field(default_factory=dict)
|
19
|
+
|
20
|
+
@dataclass_json(undefined=Undefined.EXCLUDE)
|
21
|
+
@dataclass
|
22
|
+
class ProductionCompany:
|
23
|
+
"""
|
24
|
+
Represents a production company of a movie.
|
25
|
+
|
26
|
+
Attributes:
|
27
|
+
id (int): The unique identifier of the production company.
|
28
|
+
logo_path (Optional[str]): The path to the logo of the production company.
|
29
|
+
name (str): The name of the production company.
|
30
|
+
origin_country (str): The country of origin of the production company.
|
31
|
+
"""
|
32
|
+
id: Optional[int] = None
|
33
|
+
logo_path: Optional[str] = None
|
34
|
+
name: Optional[str] = None
|
35
|
+
origin_country: Optional[str] = None
|
36
|
+
_catchall: Dict[str, str] = field(default_factory=dict)
|
37
|
+
|
38
|
+
@dataclass_json(undefined=Undefined.EXCLUDE)
|
39
|
+
@dataclass
|
40
|
+
class ProductionCountry:
|
41
|
+
"""
|
42
|
+
Represents a production country of a movie.
|
43
|
+
|
44
|
+
Attributes:
|
45
|
+
iso_3166_1 (str): The ISO 3166-1 alpha-2 code of the country.
|
46
|
+
name (str): The name of the country.
|
47
|
+
"""
|
48
|
+
iso_3166_1: Optional[str] = None
|
49
|
+
name: Optional[str] = None
|
50
|
+
_catchall: Dict[str, str] = field(default_factory=dict)
|
51
|
+
|
52
|
+
@dataclass_json(undefined=Undefined.EXCLUDE)
|
53
|
+
@dataclass
|
54
|
+
class SpokenLanguage:
|
55
|
+
"""
|
56
|
+
Represents a language spoken in a movie.
|
57
|
+
|
58
|
+
Attributes:
|
59
|
+
english_name (str): The English name of the language.
|
60
|
+
iso_639_1 (str): The ISO 639-1 code of the language.
|
61
|
+
name (str): The name of the language in the original language.
|
62
|
+
"""
|
63
|
+
english_name: Optional[str] = None
|
64
|
+
iso_639_1: Optional[str] = None
|
65
|
+
name: Optional[str] = None
|
66
|
+
_catchall: Dict[str, str] = field(default_factory=dict)
|
67
|
+
|
68
|
+
@dataclass_json(undefined=Undefined.EXCLUDE)
|
69
|
+
@dataclass
|
70
|
+
class AlternativeTitle:
|
71
|
+
"""
|
72
|
+
Represents an alternative title of a movie.
|
73
|
+
|
74
|
+
Attributes:
|
75
|
+
iso_3166_1 (str): The ISO 3166-1 alpha-2 code of the country where the alternative title is used.
|
76
|
+
title (str): The alternative title.
|
77
|
+
type (str): The type of the alternative title.
|
78
|
+
"""
|
79
|
+
iso_3166_1: Optional[str] = None
|
80
|
+
title: Optional[str] = None
|
81
|
+
type: Optional[str] = None
|
82
|
+
_catchall: Dict[str, str] = field(default_factory=dict)
|
83
|
+
|
84
|
+
@dataclass_json(undefined=Undefined.EXCLUDE)
|
85
|
+
@dataclass
|
86
|
+
class AlternativeTitles:
|
87
|
+
"""
|
88
|
+
Represents a collection of alternative titles of a movie.
|
89
|
+
|
90
|
+
Attributes:
|
91
|
+
titles (List[AlternativeTitle]): The list of alternative titles.
|
92
|
+
"""
|
93
|
+
titles: Optional[List[AlternativeTitle]] = None
|
94
|
+
_catchall: Dict[str, str] = field(default_factory=dict)
|
95
|
+
|
96
|
+
def __post_init__(self):
|
97
|
+
self.titles = [AlternativeTitle(**title) if isinstance(title, dict) else title for title in self.titles]
|
98
|
+
|
99
|
+
@dataclass_json(undefined=Undefined.EXCLUDE)
|
100
|
+
@dataclass
|
101
|
+
class AlternativeTitlesShow:
|
102
|
+
"""
|
103
|
+
Represents a collection of alternative titles of a movie.
|
104
|
+
|
105
|
+
Attributes:
|
106
|
+
results (List[AlternativeTitle]): The list of alternative titles.
|
107
|
+
"""
|
108
|
+
results: Optional[List[AlternativeTitle]] = None
|
109
|
+
_catchall: Dict[str, str] = field(default_factory=dict)
|
110
|
+
|
111
|
+
def __post_init__(self):
|
112
|
+
self.results = [AlternativeTitle(**title) if isinstance(title, dict) else title for title in self.results]
|
113
|
+
|
114
|
+
@dataclass_json(undefined=Undefined.EXCLUDE)
|
115
|
+
@dataclass
|
116
|
+
class TmdbMovie:
|
117
|
+
"""
|
118
|
+
Represents a movie.
|
119
|
+
|
120
|
+
Attributes:
|
121
|
+
adult (bool): Whether the movie is for adults.
|
122
|
+
backdrop_path (str): The path to the backdrop image of the movie.
|
123
|
+
belongs_to_collection (Optional[str]): The collection the movie belongs to.
|
124
|
+
budget (int): The budget of the movie.
|
125
|
+
genres (List[Genre]): The genres of the movie.
|
126
|
+
homepage (str): The homepage of the movie.
|
127
|
+
id (int): The unique identifier of the movie.
|
128
|
+
imdb_id (str): The IMDb identifier of the movie.
|
129
|
+
original_language (str): The original language of the movie.
|
130
|
+
original_title (str): The original title of the movie.
|
131
|
+
overview (str): The overview of the movie.
|
132
|
+
popularity (float): The popularity of the movie.
|
133
|
+
poster_path (str): The path to the poster image of the movie.
|
134
|
+
production_companies (List[ProductionCompany]): The production companies of the movie.
|
135
|
+
production_countries (List[ProductionCountry]): The production countries of the movie.
|
136
|
+
release_date (str): The release date of the movie.
|
137
|
+
revenue (int): The revenue of the movie.
|
138
|
+
runtime (int): The runtime of the movie.
|
139
|
+
spoken_languages (List[SpokenLanguage]): The languages spoken in the movie.
|
140
|
+
status (str): The status of the movie.
|
141
|
+
tagline (str): The tagline of the movie.
|
142
|
+
title (str): The title of the movie.
|
143
|
+
video (bool): Whether the movie has a video.
|
144
|
+
vote_average (float): The average vote of the movie.
|
145
|
+
vote_count (int): The vote count of the movie.
|
146
|
+
alternative_titles (AlternativeTitles): The alternative titles of the movie.
|
147
|
+
_catchall (dict): A dictionary to catch all other fields not explicitly defined in the data class.
|
148
|
+
"""
|
149
|
+
adult: Optional[bool] = None
|
150
|
+
backdrop_path: Optional[str] = None
|
151
|
+
belongs_to_collection: Optional[str] = None
|
152
|
+
budget: Optional[int] = None
|
153
|
+
genres: Optional[List[Genre]] = None
|
154
|
+
homepage: Optional[str] = None
|
155
|
+
id: Optional[int] = None
|
156
|
+
imdb_id: Optional[str] = None
|
157
|
+
original_language: Optional[str] = None
|
158
|
+
original_title: Optional[str] = None
|
159
|
+
overview: Optional[str] = None
|
160
|
+
popularity: Optional[float] = None
|
161
|
+
poster_path: Optional[str] = None
|
162
|
+
production_companies: Optional[List[ProductionCompany]] = None
|
163
|
+
production_countries: Optional[List[ProductionCountry]] = None
|
164
|
+
release_date: Optional[str] = None
|
165
|
+
revenue: Optional[int] = None
|
166
|
+
runtime: Optional[int] = None
|
167
|
+
spoken_languages: Optional[List[SpokenLanguage]] = None
|
168
|
+
status: Optional[str] = None
|
169
|
+
tagline: Optional[str] = None
|
170
|
+
title: Optional[str] = None
|
171
|
+
video: Optional[bool] = None
|
172
|
+
vote_average: Optional[float] = None
|
173
|
+
vote_count: Optional[int] = None
|
174
|
+
alternative_titles: Optional[AlternativeTitles] = None
|
175
|
+
_catchall: Dict[str, str] = field(default_factory=dict)
|
176
|
+
|
177
|
+
def __post_init__(self):
|
178
|
+
self.genres = [Genre(**genre) if isinstance(genre, dict) else genre for genre in self.genres]
|
179
|
+
self.production_companies = [ProductionCompany(**pc) if isinstance(pc, dict) else pc for pc in self.production_companies]
|
180
|
+
self.production_countries = [ProductionCountry(**pc) if isinstance(pc, dict) else pc for pc in self.production_countries]
|
181
|
+
self.spoken_languages = [SpokenLanguage(**sl) if isinstance(sl, dict) else sl for sl in self.spoken_languages]
|
182
|
+
self.alternative_titles = AlternativeTitles(**self.alternative_titles) if isinstance(self.alternative_titles, dict) else self.alternative_titles
|
183
|
+
|
184
|
+
@dataclass_json(undefined=Undefined.EXCLUDE)
|
185
|
+
@dataclass
|
186
|
+
class Creator:
|
187
|
+
"""
|
188
|
+
Represents a creator of a TV show.
|
189
|
+
|
190
|
+
Attributes:
|
191
|
+
id (int): The unique identifier of the creator.
|
192
|
+
credit_id (str): The credit identifier of the creator.
|
193
|
+
name (str): The name of the creator.
|
194
|
+
gender (int): The gender of the creator.
|
195
|
+
profile_path (str): The path to the profile of the creator.
|
196
|
+
"""
|
197
|
+
id: Optional[int] = None
|
198
|
+
credit_id: Optional[str] = None
|
199
|
+
name: Optional[str] = None
|
200
|
+
gender: Optional[int] = None
|
201
|
+
profile_path: Optional[str] = None
|
202
|
+
_catchall: Dict[str, str] = field(default_factory=dict)
|
203
|
+
|
204
|
+
@dataclass_json(undefined=Undefined.EXCLUDE)
|
205
|
+
@dataclass
|
206
|
+
class Network:
|
207
|
+
"""
|
208
|
+
Represents a network of a TV show.
|
209
|
+
|
210
|
+
Attributes:
|
211
|
+
id (int): The unique identifier of the network.
|
212
|
+
logo_path (str): The path to the logo of the network.
|
213
|
+
name (str): The name of the network.
|
214
|
+
origin_country (str): The country of origin of the network.
|
215
|
+
"""
|
216
|
+
id: Optional[int] = None
|
217
|
+
logo_path: Optional[str] = None
|
218
|
+
name: Optional[str] = None
|
219
|
+
origin_country: Optional[str] = None
|
220
|
+
_catchall: Dict[str, str] = field(default_factory=dict)
|
221
|
+
|
222
|
+
@dataclass_json(undefined=Undefined.EXCLUDE)
|
223
|
+
@dataclass
|
224
|
+
class Season:
|
225
|
+
"""
|
226
|
+
Represents a season of a TV show.
|
227
|
+
|
228
|
+
Attributes:
|
229
|
+
air_date (str): The air date of the season.
|
230
|
+
episode_count (int): The number of episodes in the season.
|
231
|
+
id (int): The unique identifier of the season.
|
232
|
+
name (str): The name of the season.
|
233
|
+
overview (str): The overview of the season.
|
234
|
+
poster_path (str): The path to the poster of the season.
|
235
|
+
season_number (int): The number of the season.
|
236
|
+
vote_average (float): The average vote of the season.
|
237
|
+
"""
|
238
|
+
air_date: Optional[str] = None
|
239
|
+
episode_count: Optional[int] = None
|
240
|
+
id: Optional[int] = None
|
241
|
+
name: Optional[str] = None
|
242
|
+
overview: Optional[str] = None
|
243
|
+
poster_path: Optional[str] = None
|
244
|
+
season_number: Optional[int] = None
|
245
|
+
vote_average: Optional[float] = None
|
246
|
+
_catchall: Dict[str, str] = field(default_factory=dict)
|
247
|
+
|
248
|
+
@dataclass_json(undefined=Undefined.EXCLUDE)
|
249
|
+
@dataclass
|
250
|
+
class Episode:
|
251
|
+
"""
|
252
|
+
Represents an episode of a TV show.
|
253
|
+
|
254
|
+
Attributes:
|
255
|
+
id (int): The unique identifier of the episode.
|
256
|
+
name (str): The name of the episode.
|
257
|
+
overview (str): The overview of the episode.
|
258
|
+
vote_average (float): The average vote of the episode.
|
259
|
+
vote_count (int): The vote count of the episode.
|
260
|
+
air_date (str): The air date of the episode.
|
261
|
+
episode_number (int): The number of the episode.
|
262
|
+
episode_type (str): The type of the episode.
|
263
|
+
production_code (str): The production code of the episode.
|
264
|
+
runtime (int): The runtime of the episode.
|
265
|
+
season_number (int): The number of the season.
|
266
|
+
show_id (int): The id of the show.
|
267
|
+
still_path (str): The path to the still of the episode.
|
268
|
+
"""
|
269
|
+
id: Optional[int] = None
|
270
|
+
name: Optional[str] = None
|
271
|
+
overview: Optional[str] = None
|
272
|
+
vote_average: Optional[float] = None
|
273
|
+
vote_count: Optional[int] = None
|
274
|
+
air_date: Optional[str] = None
|
275
|
+
episode_number: Optional[int] = None
|
276
|
+
episode_type: Optional[str] = None
|
277
|
+
production_code: Optional[str] = None
|
278
|
+
runtime: Optional[int] = None
|
279
|
+
season_number: Optional[int] = None
|
280
|
+
show_id: Optional[int] = None
|
281
|
+
still_path: Optional[str] = None
|
282
|
+
_catchall: Dict[str, str] = field(default_factory=dict)
|
283
|
+
|
284
|
+
@dataclass_json(undefined=Undefined.EXCLUDE)
|
285
|
+
@dataclass
|
286
|
+
class ExternalIds:
|
287
|
+
"""
|
288
|
+
Represents the external IDs of a TV show.
|
289
|
+
|
290
|
+
Attributes:
|
291
|
+
imdb_id (str): The IMDb ID of the TV show.
|
292
|
+
freebase_mid (str): The Freebase MID of the TV show.
|
293
|
+
freebase_id (str): The Freebase ID of the TV show.
|
294
|
+
tvdb_id (int): The TVDB ID of the TV show.
|
295
|
+
tvrage_id (int): The TVRage ID of the TV show.
|
296
|
+
wikidata_id (str): The Wikidata ID of the TV show.
|
297
|
+
facebook_id (str): The Facebook ID of the TV show.
|
298
|
+
instagram_id (str): The Instagram ID of the TV show.
|
299
|
+
twitter_id (str): The Twitter ID of the TV show.
|
300
|
+
"""
|
301
|
+
imdb_id: Optional[str] = None
|
302
|
+
freebase_mid: Optional[str] = None
|
303
|
+
freebase_id: Optional[str] = None
|
304
|
+
tvdb_id: Optional[int] = None
|
305
|
+
tvrage_id: Optional[int] = None
|
306
|
+
wikidata_id: Optional[str] = None
|
307
|
+
facebook_id: Optional[str] = None
|
308
|
+
instagram_id: Optional[str] = None
|
309
|
+
twitter_id: Optional[str] = None
|
310
|
+
_catchall: Dict[str, str] = field(default_factory=dict)
|
311
|
+
|
312
|
+
|
313
|
+
@dataclass_json(undefined=Undefined.EXCLUDE)
|
314
|
+
@dataclass
|
315
|
+
class TmdbShow:
|
316
|
+
"""
|
317
|
+
Represents a TV show.
|
318
|
+
|
319
|
+
Attributes:
|
320
|
+
created_by (List[Creator]): The creators of the TV show.
|
321
|
+
episode_run_time (List[int]): The runtime of the episodes of the TV show.
|
322
|
+
first_air_date (str): The first air date of the TV show.
|
323
|
+
in_production (bool): Whether the TV show is in production.
|
324
|
+
languages (List[str]): The languages of the TV show.
|
325
|
+
last_air_date (str): The last air date of the TV show.
|
326
|
+
last_episode_to_air (Episode): The last episode to air.
|
327
|
+
networks (List[Network]): The networks of the TV show.
|
328
|
+
number_of_episodes (int): The number of episodes of the TV show.
|
329
|
+
number_of_seasons (int): The number of seasons of the TV show.
|
330
|
+
origin_country (List[str]): The countries of origin of the TV show.
|
331
|
+
original_name (str): The original name of the TV show.
|
332
|
+
seasons (List[Season]): The seasons of the TV show.
|
333
|
+
type (str): The type of the TV show.
|
334
|
+
"""
|
335
|
+
created_by: Optional[List[Creator]] = None
|
336
|
+
episode_run_time: Optional[List[int]] = None
|
337
|
+
first_air_date: Optional[str] = None
|
338
|
+
in_production: Optional[bool] = None
|
339
|
+
languages: Optional[List[str]] = None
|
340
|
+
last_air_date: Optional[str] = None
|
341
|
+
last_episode_to_air: Optional[Episode] = None
|
342
|
+
networks: Optional[List[Network]] = None
|
343
|
+
number_of_episodes: Optional[int] = None
|
344
|
+
number_of_seasons: Optional[int] = None
|
345
|
+
origin_country: Optional[List[str]] = None
|
346
|
+
original_name: Optional[str] = None
|
347
|
+
seasons: Optional[List[Season]] = None
|
348
|
+
type: Optional[str] = None
|
349
|
+
adult: Optional[bool] = None
|
350
|
+
backdrop_path: Optional[str] = None
|
351
|
+
belongs_to_collection: Optional[str] = None
|
352
|
+
budget: Optional[int] = None
|
353
|
+
genres: Optional[List[Genre]] = None
|
354
|
+
homepage: Optional[str] = None
|
355
|
+
id: Optional[int] = None
|
356
|
+
imdb_id: Optional[str] = None
|
357
|
+
original_language: Optional[str] = None
|
358
|
+
overview: Optional[str] = None
|
359
|
+
popularity: Optional[float] = None
|
360
|
+
poster_path: Optional[str] = None
|
361
|
+
production_companies: Optional[List[ProductionCompany]] = None
|
362
|
+
production_countries: Optional[List[ProductionCountry]] = None
|
363
|
+
spoken_languages: Optional[List[SpokenLanguage]] = None
|
364
|
+
status: Optional[str] = None
|
365
|
+
tagline: Optional[str] = None
|
366
|
+
vote_average: Optional[float] = None
|
367
|
+
vote_count: Optional[int] = None
|
368
|
+
alternative_titles: Optional[AlternativeTitlesShow] = None
|
369
|
+
external_ids: Optional[ExternalIds] = None
|
370
|
+
|
371
|
+
def __post_init__(self):
|
372
|
+
self.created_by = [Creator(**creator) if isinstance(creator, dict) else creator for creator in self.created_by]
|
373
|
+
self.last_episode_to_air = Episode(**self.last_episode_to_air) if isinstance(self.last_episode_to_air, dict) else self.last_episode_to_air
|
374
|
+
self.networks = [Network(**network) if isinstance(network, dict) else network for network in self.networks]
|
375
|
+
self.seasons = [Season(**season) if isinstance(season, dict) else season for season in self.seasons]
|
376
|
+
|
377
|
+
self.genres = [Genre(**genre) if isinstance(genre, dict) else genre for genre in self.genres]
|
378
|
+
self.production_companies = [ProductionCompany(**pc) if isinstance(pc, dict) else pc for pc in self.production_companies]
|
379
|
+
self.production_countries = [ProductionCountry(**pc) if isinstance(pc, dict) else pc for pc in self.production_countries]
|
380
|
+
self.spoken_languages = [SpokenLanguage(**sl) if isinstance(sl, dict) else sl for sl in self.spoken_languages]
|
381
|
+
self.alternative_titles = AlternativeTitlesShow(**self.alternative_titles) if isinstance(self.alternative_titles, dict) else self.alternative_titles
|
382
|
+
self.external_ids = ExternalIds(**self.external_ids) if isinstance(self.external_ids, dict) else self.external_ids
|
383
|
+
|
384
|
+
|
385
|
+
|
386
|
+
|
387
|
+
from dataclasses import dataclass, field
|
388
|
+
from typing import List, Optional
|
389
|
+
from dataclasses_json import dataclass_json, Undefined
|
390
|
+
|
391
|
+
@dataclass_json(undefined=Undefined.EXCLUDE)
|
392
|
+
@dataclass
|
393
|
+
class TmdbPerson:
|
394
|
+
job: Optional[str] = None
|
395
|
+
department: Optional[str] = None
|
396
|
+
credit_id: Optional[str] = None
|
397
|
+
adult: Optional[bool] = None
|
398
|
+
gender: Optional[int] = None
|
399
|
+
id: Optional[int] = None
|
400
|
+
known_for_department: Optional[str] = None
|
401
|
+
name: Optional[str] = None
|
402
|
+
original_name: Optional[str] = None
|
403
|
+
popularity: Optional[float] = None
|
404
|
+
profile_path: Optional[str] = None
|
405
|
+
|
406
|
+
@dataclass_json(undefined=Undefined.EXCLUDE)
|
407
|
+
@dataclass
|
408
|
+
class SeasonEpisode:
|
409
|
+
air_date: Optional[str] = None
|
410
|
+
episode_number: Optional[int] = None
|
411
|
+
episode_type: Optional[str] = None
|
412
|
+
id: Optional[int] = None
|
413
|
+
name: Optional[str] = None
|
414
|
+
overview: Optional[str] = None
|
415
|
+
production_code: Optional[str] = None
|
416
|
+
runtime: Optional[int] = None
|
417
|
+
season_number: Optional[int] = None
|
418
|
+
show_id: Optional[int] = None
|
419
|
+
still_path: Optional[str] = None
|
420
|
+
vote_average: Optional[float] = None
|
421
|
+
vote_count: Optional[int] = None
|
422
|
+
crew: Optional[List[TmdbPerson]] = None
|
423
|
+
guest_stars: Optional[List[TmdbPerson]] = None
|
424
|
+
|
425
|
+
def __post_init__(self):
|
426
|
+
self.crew = [TmdbPerson(**creator) if isinstance(creator, dict) else creator for creator in self.crew]
|
427
|
+
self.guest_stars = [TmdbPerson(**creator) if isinstance(creator, dict) else creator for creator in self.guest_stars]
|
428
|
+
|
429
|
+
@dataclass_json(undefined=Undefined.EXCLUDE)
|
430
|
+
@dataclass
|
431
|
+
class Network:
|
432
|
+
name: Optional[str] = None
|
433
|
+
id: Optional[int] = None
|
434
|
+
logo_path: Optional[str] = None
|
435
|
+
origin_country: Optional[str] = None
|
436
|
+
|
437
|
+
@dataclass_json(undefined=Undefined.EXCLUDE)
|
438
|
+
@dataclass
|
439
|
+
class TmdbSeason:
|
440
|
+
air_date: Optional[str] = None
|
441
|
+
id: Optional[int] = None
|
442
|
+
name: Optional[str] = None
|
443
|
+
season_number: Optional[int] = None
|
444
|
+
vote_average: Optional[float] = None
|
445
|
+
episodes: Optional[List[SeasonEpisode]] = None
|
446
|
+
|
447
|
+
def __post_init__(self):
|
448
|
+
self.episodes = [SeasonEpisode(**episode) if isinstance(episode, dict) else episode for episode in self.episodes]
|
449
|
+
|
450
|
+
def show_from_json(json_str: str) -> TmdbShow:
|
451
|
+
try:
|
452
|
+
return TmdbShow(**json.loads(json_str))
|
453
|
+
except json.JSONDecodeError as e:
|
454
|
+
raise f"Error decoding JSON: {e}"
|
455
|
+
|
456
|
+
def movie_from_json(json_str: str) -> TmdbMovie:
|
457
|
+
try:
|
458
|
+
return TmdbMovie(**json.loads(json_str))
|
459
|
+
except json.JSONDecodeError as e:
|
460
|
+
raise f"Error decoding JSON: {e}"
|