sp4rky-devine-services/services/CRKL/__init__.py
2025-04-09 15:07:35 -06:00

138 lines
5.0 KiB
Python

from http.cookiejar import CookieJar
from typing import Optional, Union
import click
import requests
from devine.core.service import Service
from devine.core.titles import Movies, Movie, Titles_T, Title_T, Series, Episode
from devine.core.constants import AnyTrack
from devine.core.credential import Credential
from devine.core.tracks import Chapters, Tracks, Chapter
from devine.core.manifests import DASH
class CRKL(Service):
"""
Service code for Crackle
Written by TPD94
Authorization: None
Security: FHD@L3
"""
GEOFENCE = ('US',)
# Static method, this method belongs to the class
@staticmethod
# The command name, must much the service tag (and by extension the service folder)
@click.command(name="CRKL", short_help="https://www.crackle.com", help=__doc__)
# Using series ID for hulu
@click.argument("title", type=str)
# Pass the context back to the CLI with arguments
@click.pass_context
def cli(ctx, **kwargs):
return CRKL(ctx, **kwargs)
# Accept the CLI arguments by overriding the constructor (The __init__() method)
def __init__(self, ctx, title):
# Pass the series_id argument to self so it's accessable across all methods
self.title = title
# Overriding the constructor
super().__init__(ctx)
def authenticate(self, cookies: Optional[CookieJar] = None, credential: Optional[Credential] = None) -> None:
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0',
'Accept': '*/*',
'Accept-Language': 'en-US,en;q=0.5',
'Referer': 'https://www.crackle.com/',
'Content-Type': 'application/json',
'x-crackle-brand': 'crackle',
'x-crackle-platform': '5FE67CCA-069A-42C6-A20F-4B47A8054D46',
'x-crackle-region': 'us',
'x-crackle-locale': 'en-us',
'x-crackle-apiversion': 'v2.0.0',
}
self.session.headers.update(headers)
def get_titles(self) -> Titles_T:
video_type = self.session.get(url=f'https://prod-api.crackle.com/content/{self.title}').json()
if video_type['data']['type'] == 'Movie':
movie_metadata = video_type['data']['metadata'][0]
movie_id = self.session.get(url=f'https://prod-api.crackle.com/content/{self.title}/children').json()['data'][0]['id']
return Movies([Movie(
id_=movie_id,
service=self.__class__,
name=movie_metadata['title'],
year=None,
language=None
)])
elif video_type['data']['type'] == 'Series':
season_ids = []
seasons_metadata = self.session.get(url=f'https://prod-api.crackle.com/content/{self.title}/children').json()
for season in seasons_metadata['data']:
season_ids.append(season['id'])
episodes = []
for season in season_ids:
episodes_metadata = self.session.get(url=f'https://prod-api.crackle.com/content/{season}/children').json()
for episode in episodes_metadata['data']:
episodes.append(Episode(
id_=episode['id'],
service=self.__class__,
title=video_type['data']['metadata'][0]['title'],
season=episode['seasonNumber'],
number=episode['episodeNumber'],
name=episode['title'],
year=None,
language=None
))
return Series(episodes)
def get_tracks(self, title: Title_T) -> Tracks:
title_metadata = self.session.get(url=f'https://prod-api.crackle.com/playback/vod/{title.id}').json()
for manifest_url in title_metadata['data']['streams']:
if manifest_url['type'] == 'dash-widevine' or manifest_url['type'] == 'dash_widevine':
index_url = manifest_url['url']
base_target_url = index_url[:index_url.find(".com")+len(".com")]
mpd_url_metadata = self.session.post(url=index_url).json()['manifestUrl']
final_url = base_target_url + mpd_url_metadata
return DASH.from_url(final_url).to_tracks(language="en")
def get_chapters(self, title: Title_T) -> Chapters:
title_metadata = self.session.get(url=f'https://prod-api.crackle.com/content/{title.id}').json()
return Chapters([Chapter(timestamp=title_metadata['data']['assets']['chapters'][1]['breakpoints'][0]['smpteStart'][:8])])
def get_widevine_license(self, *, challenge: bytes, title: Title_T, track: AnyTrack) -> Optional[Union[bytes, str]]:
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0',
'Accept': '*/*',
'Accept-Language': 'en-US,en;q=0.5',
}
return requests.post(url='https://widevine-license.crackle.com/', data=challenge, headers=headers).content