simple-justwatch-python-api 0.15__tar.gz → 0.16__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.
- {simple_justwatch_python_api-0.15 → simple_justwatch_python_api-0.16}/PKG-INFO +1 -1
- {simple_justwatch_python_api-0.15 → simple_justwatch_python_api-0.16}/pyproject.toml +1 -1
- {simple_justwatch_python_api-0.15 → simple_justwatch_python_api-0.16}/src/simple_justwatch_python_api.egg-info/PKG-INFO +1 -1
- {simple_justwatch_python_api-0.15 → simple_justwatch_python_api-0.16}/src/simplejustwatchapi/main.py +6 -5
- {simple_justwatch_python_api-0.15 → simple_justwatch_python_api-0.16}/src/simplejustwatchapi/query.py +180 -0
- {simple_justwatch_python_api-0.15 → simple_justwatch_python_api-0.16}/LICENSE +0 -0
- {simple_justwatch_python_api-0.15 → simple_justwatch_python_api-0.16}/README.md +0 -0
- {simple_justwatch_python_api-0.15 → simple_justwatch_python_api-0.16}/setup.cfg +0 -0
- {simple_justwatch_python_api-0.15 → simple_justwatch_python_api-0.16}/src/simple_justwatch_python_api.egg-info/SOURCES.txt +0 -0
- {simple_justwatch_python_api-0.15 → simple_justwatch_python_api-0.16}/src/simple_justwatch_python_api.egg-info/dependency_links.txt +0 -0
- {simple_justwatch_python_api-0.15 → simple_justwatch_python_api-0.16}/src/simple_justwatch_python_api.egg-info/requires.txt +0 -0
- {simple_justwatch_python_api-0.15 → simple_justwatch_python_api-0.16}/src/simple_justwatch_python_api.egg-info/top_level.txt +0 -0
- {simple_justwatch_python_api-0.15 → simple_justwatch_python_api-0.16}/src/simplejustwatchapi/__init__.py +0 -0
- {simple_justwatch_python_api-0.15 → simple_justwatch_python_api-0.16}/src/simplejustwatchapi/justwatch.py +0 -0
|
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "simple-justwatch-python-api"
|
|
7
7
|
authors = [{name = "Electronic Mango", email = "78230210+Electronic-Mango@users.noreply.github.com"}]
|
|
8
|
-
version = "0.
|
|
8
|
+
version = "0.16"
|
|
9
9
|
description="A simple JustWatch Python API"
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
license = {file = "LICENSE"}
|
{simple_justwatch_python_api-0.15 → simple_justwatch_python_api-0.16}/src/simplejustwatchapi/main.py
RENAMED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
1
2
|
from itertools import product
|
|
2
3
|
|
|
3
4
|
from justwatch import details, offers_for_countries, search
|
|
4
5
|
|
|
5
6
|
|
|
6
|
-
from dataclasses import dataclass
|
|
7
|
-
|
|
8
|
-
|
|
9
7
|
@dataclass
|
|
10
8
|
class Base:
|
|
11
9
|
base_field_1: int
|
|
@@ -22,9 +20,12 @@ def main():
|
|
|
22
20
|
# result = details("ts4") # Breaking Bad
|
|
23
21
|
# result = details("tss25") # Breaking Bad S5
|
|
24
22
|
# result = details("tse411") # Breaking Bad S5E1
|
|
25
|
-
# result = details("tm10")
|
|
26
|
-
|
|
23
|
+
# result = details("tm10")
|
|
24
|
+
# print(result) # The Matrix
|
|
25
|
+
# result = details("ts20711") # The Simpsons
|
|
26
|
+
# result = details("tm118477") # Haker (2002)
|
|
27
27
|
# result = search("The Simpsons")
|
|
28
|
+
result = search("The Matrix", "US", "en", 3, True)
|
|
28
29
|
print(result)
|
|
29
30
|
print()
|
|
30
31
|
|
|
@@ -100,6 +100,40 @@ fragment TitleDetails on MovieOrShow {
|
|
|
100
100
|
backdropUrl
|
|
101
101
|
__typename
|
|
102
102
|
}
|
|
103
|
+
ageCertification
|
|
104
|
+
scoring {
|
|
105
|
+
imdbScore
|
|
106
|
+
imdbVotes
|
|
107
|
+
tmdbPopularity
|
|
108
|
+
tmdbScore
|
|
109
|
+
tomatoMeter
|
|
110
|
+
certifiedFresh
|
|
111
|
+
jwRating
|
|
112
|
+
__typename
|
|
113
|
+
}
|
|
114
|
+
interactions {
|
|
115
|
+
likelistAdditions
|
|
116
|
+
dislikelistAdditions
|
|
117
|
+
__typename
|
|
118
|
+
}
|
|
119
|
+
__typename
|
|
120
|
+
}
|
|
121
|
+
streamingCharts(country: $country) {
|
|
122
|
+
edges {
|
|
123
|
+
streamingChartInfo {
|
|
124
|
+
rank
|
|
125
|
+
trend
|
|
126
|
+
trendDifference
|
|
127
|
+
daysInTop3
|
|
128
|
+
daysInTop10
|
|
129
|
+
daysInTop100
|
|
130
|
+
daysInTop1000
|
|
131
|
+
topRank
|
|
132
|
+
updatedAt
|
|
133
|
+
__typename
|
|
134
|
+
}
|
|
135
|
+
__typename
|
|
136
|
+
}
|
|
103
137
|
__typename
|
|
104
138
|
}
|
|
105
139
|
offers(country: $country, platform: WEB, filter: $filter) {
|
|
@@ -224,6 +258,72 @@ class Offer(NamedTuple):
|
|
|
224
258
|
"""List of 2-letter language codes of available audio tracks, e.g. ``["en", "pt", "de"]``."""
|
|
225
259
|
|
|
226
260
|
|
|
261
|
+
class Scoring(NamedTuple):
|
|
262
|
+
"""Parsed data related to user scoring for a single entry."""
|
|
263
|
+
|
|
264
|
+
imdb_score: float | None
|
|
265
|
+
"""IMDB score."""
|
|
266
|
+
|
|
267
|
+
imdb_votes: int | None
|
|
268
|
+
"""Number of votes on IMDB."""
|
|
269
|
+
|
|
270
|
+
tmdb_popularity: float | None
|
|
271
|
+
"""TMDB popularity score."""
|
|
272
|
+
|
|
273
|
+
tmdb_score: float | None
|
|
274
|
+
"""TMDB score."""
|
|
275
|
+
|
|
276
|
+
tomatometer: int | None
|
|
277
|
+
"""Tomatometer score on Rotten Tomatoes."""
|
|
278
|
+
|
|
279
|
+
certified_fresh: bool | None
|
|
280
|
+
"""Flag whether entry has "Certified Fresh" seal on Rotten Tomatoes."""
|
|
281
|
+
|
|
282
|
+
jw_rating: float | None
|
|
283
|
+
"""JustWatch rating."""
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class Interactions(NamedTuple):
|
|
287
|
+
"""Parsed data regarding number of likes and dislikes on JustWatch for a single entry."""
|
|
288
|
+
|
|
289
|
+
likes: int | None
|
|
290
|
+
"""Number of likes on JustWatch."""
|
|
291
|
+
|
|
292
|
+
dislikes: int | None
|
|
293
|
+
"""Number of dislikes on JustWatch."""
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
class StreamingCharts(NamedTuple):
|
|
297
|
+
"""Parsed data related to JustWatch rank for a single entry."""
|
|
298
|
+
|
|
299
|
+
rank: int
|
|
300
|
+
"""Rank on JustWatch."""
|
|
301
|
+
|
|
302
|
+
trend: str
|
|
303
|
+
"""Trend in ranking on JustWatch, ``UP``, ``DOWN``, ``STABLE``."""
|
|
304
|
+
|
|
305
|
+
trend_difference: int
|
|
306
|
+
"""Difference in rank; related to trend."""
|
|
307
|
+
|
|
308
|
+
top_rank: int
|
|
309
|
+
"""Top rank ever reached."""
|
|
310
|
+
|
|
311
|
+
days_in_top_3: int
|
|
312
|
+
"""Number of days in top 3 ranks."""
|
|
313
|
+
|
|
314
|
+
days_in_top_10: int
|
|
315
|
+
"""Number of days in top 10 ranks."""
|
|
316
|
+
|
|
317
|
+
days_in_top_100: int
|
|
318
|
+
"""Number of days in top 100 ranks."""
|
|
319
|
+
|
|
320
|
+
days_in_top_1000: int
|
|
321
|
+
"""Number of days in top 1000 ranks."""
|
|
322
|
+
|
|
323
|
+
updated: str
|
|
324
|
+
"""Date when rank data was last updated as a string, e.g.: ``2024-10-06T09:20:36.397Z``."""
|
|
325
|
+
|
|
326
|
+
|
|
227
327
|
class MediaEntry(NamedTuple):
|
|
228
328
|
"""Parsed response from JustWatch GraphQL API for "GetSearchTitles" query for single entry."""
|
|
229
329
|
|
|
@@ -269,6 +369,18 @@ class MediaEntry(NamedTuple):
|
|
|
269
369
|
backdrops: list[str]
|
|
270
370
|
"""List of URLs for backdrops (full screen images to use as background)."""
|
|
271
371
|
|
|
372
|
+
age_certification: str | None
|
|
373
|
+
"""Age rating as a string, e.g.: "R", "TV-14"."""
|
|
374
|
+
|
|
375
|
+
scoring: Scoring | None
|
|
376
|
+
"""Scoring data."""
|
|
377
|
+
|
|
378
|
+
interactions: Interactions | None
|
|
379
|
+
"""Interactions (likes/dislikes) data."""
|
|
380
|
+
|
|
381
|
+
streaming_charts: StreamingCharts | None
|
|
382
|
+
"""JustWatch charts/ranks data."""
|
|
383
|
+
|
|
272
384
|
offers: list[Offer]
|
|
273
385
|
"""List of available offers for this entry, empty if there are no available offers."""
|
|
274
386
|
|
|
@@ -477,6 +589,10 @@ def _parse_entry(json: any) -> MediaEntry:
|
|
|
477
589
|
poster_url_field = content.get("posterUrl")
|
|
478
590
|
poster = _IMAGES_URL + poster_url_field if poster_url_field else None
|
|
479
591
|
backdrops = [_IMAGES_URL + bd.get("backdropUrl") for bd in content.get("backdrops", []) if bd]
|
|
592
|
+
age_certification = content.get("ageCertification")
|
|
593
|
+
scoring = _parse_scores(content.get("scoring"))
|
|
594
|
+
interactions = _parse_interactions(content.get("interactions"))
|
|
595
|
+
streaming_charts = _parse_streaming_charts(json)
|
|
480
596
|
offers = [_parse_offer(offer) for offer in json.get("offers", []) if offer]
|
|
481
597
|
return MediaEntry(
|
|
482
598
|
entry_id,
|
|
@@ -493,10 +609,74 @@ def _parse_entry(json: any) -> MediaEntry:
|
|
|
493
609
|
tmdb_id,
|
|
494
610
|
poster,
|
|
495
611
|
backdrops,
|
|
612
|
+
age_certification,
|
|
613
|
+
scoring,
|
|
614
|
+
interactions,
|
|
615
|
+
streaming_charts,
|
|
496
616
|
offers,
|
|
497
617
|
)
|
|
498
618
|
|
|
499
619
|
|
|
620
|
+
def _parse_scores(json: any) -> Scoring | None:
|
|
621
|
+
if not json:
|
|
622
|
+
return None
|
|
623
|
+
imdb_score = json.get("imdbScore")
|
|
624
|
+
imdb_votes = json.get("imdbVotes")
|
|
625
|
+
tmdb_popularity = json.get("tmdbPopularity")
|
|
626
|
+
tmdb_score = json.get("tmdbScore")
|
|
627
|
+
tomatometer = json.get("tomatoMeter")
|
|
628
|
+
certified_fresh = json.get("certifiedFresh")
|
|
629
|
+
jw_rating = json.get("jwRating")
|
|
630
|
+
return Scoring(
|
|
631
|
+
imdb_score,
|
|
632
|
+
int(imdb_votes) if imdb_votes is not None else None,
|
|
633
|
+
tmdb_popularity,
|
|
634
|
+
tmdb_score,
|
|
635
|
+
int(tomatometer) if tomatometer is not None else None,
|
|
636
|
+
certified_fresh,
|
|
637
|
+
jw_rating,
|
|
638
|
+
)
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
def _parse_interactions(json: any) -> Interactions | None:
|
|
642
|
+
if not json:
|
|
643
|
+
return None
|
|
644
|
+
likes = json.get("likelistAdditions")
|
|
645
|
+
dislikes = json.get("dislikelistAdditions")
|
|
646
|
+
return Interactions(likes, dislikes)
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
def _parse_streaming_charts(json: any) -> StreamingCharts | None:
|
|
650
|
+
if (
|
|
651
|
+
not (streaming_chart_info := json.get("streamingCharts", {}).get("edges"))
|
|
652
|
+
or not (streaming_chart_info := streaming_chart_info[0].get("streamingChartInfo"))
|
|
653
|
+
# Getting final info is awkward, I think this in general can return a list when searching
|
|
654
|
+
# for ranks for multiple entries. In this case, to unify searching and displaying details,
|
|
655
|
+
# it's always getting single element in a list.
|
|
656
|
+
):
|
|
657
|
+
return None
|
|
658
|
+
rank = streaming_chart_info.get("rank")
|
|
659
|
+
trend = streaming_chart_info.get("trend")
|
|
660
|
+
trend_difference = streaming_chart_info.get("trendDifference")
|
|
661
|
+
top_rank = streaming_chart_info.get("topRank")
|
|
662
|
+
days_in_top_3 = streaming_chart_info.get("daysInTop3")
|
|
663
|
+
days_in_top_10 = streaming_chart_info.get("daysInTop10")
|
|
664
|
+
days_in_top_100 = streaming_chart_info.get("daysInTop100")
|
|
665
|
+
days_in_top_1000 = streaming_chart_info.get("daysInTop1000")
|
|
666
|
+
updated = streaming_chart_info.get("updatedAt")
|
|
667
|
+
return StreamingCharts(
|
|
668
|
+
rank,
|
|
669
|
+
trend,
|
|
670
|
+
trend_difference,
|
|
671
|
+
top_rank,
|
|
672
|
+
days_in_top_3,
|
|
673
|
+
days_in_top_10,
|
|
674
|
+
days_in_top_100,
|
|
675
|
+
days_in_top_1000,
|
|
676
|
+
updated,
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
|
|
500
680
|
def _parse_offer(json: any) -> Offer:
|
|
501
681
|
id = json.get("id")
|
|
502
682
|
monetization_type = json.get("monetizationType")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|