from http.cookiejar import CookieJar from typing import Optional, Union import click import uuid 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 from devine.core.manifests import DASH class HM(Service): """ Service code for Hallmark Now Written by TPD94 Authorization: Cookies 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="HM", short_help="https://www.hmnow.com/", help=__doc__) # Using series ID for crunchyroll @click.argument("title", type=str) # Option if it is a movie @click.option("--movie", is_flag=True, help="Specify if it's a movie") # Pass the context back to the CLI with arguments @click.pass_context def cli(ctx, **kwargs): return HM(ctx, **kwargs) # Accept the CLI arguments by overriding the constructor (The __init__() method) def __init__(self, ctx, title, movie): # Pass the series_id argument to self so it's accessable across all methods self.title = title # Set boolean if movie was used self.movie = movie # Overriding the constructor super().__init__(ctx) # Define the authenticate function def authenticate(self, cookies: Optional[CookieJar] = None, credential: Optional[Credential] = None) -> None: self.session.cookies.update(cookies) def get_titles(self) -> Titles_T: headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', 'Accept': 'application/json, text/plain, */*', 'Accept-Language': 'en-US,en;q=0.5', 'X-Device-Info': f'{{"id":"{uuid.uuid4()}","hardware":{{"manufacturer":"Browser (Desktop)","model":"Firefox","version":"124.0"}},"os":{{"name":"Windows","version":"10"}},"display":{{"width":1920,"height":1080}},"legal":{{}}', } if self.movie: movie_metadata = self.session.get(url=f'https://www.hmnow.com/api/core/catalog/item/{self.title}', headers=headers).json() return Movies([Movie( id_=movie_metadata['data']['id'], service=self.__class__, name=movie_metadata['data']['title'], year=movie_metadata['data']['releaseDate'][:4], language="en", data={'playbackUrl': movie_metadata['data']['video']['playback']} )]) else: series_metadata = self.session.get(url=f'https://www.hmnow.com/api/core/catalog/collection/{self.title}', headers=headers).json() season_ids = [] for season in series_metadata['data']: season_ids.append(season['id']) episodes = [] for season in season_ids: season_metadata = self.session.get(url=f'https://www.hmnow.com/api/core/catalog/collection/{season}', headers=headers).json() for episode in season_metadata['data']: episodes.append(Episode( id_=episode['id'], service=self.__class__, title=season_metadata['collection']['tvShow']['title'], season=episode['tvShowSeason']['season']['number'], number=episode['episode']['number'], name=episode['title'], year=episode['releaseDate'][:4], language="en", data={'playbackUrl': episode['video']['playback']} )) return Series(episodes) def get_tracks(self, title: Title_T) -> Tracks: headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', 'Accept': 'application/json, text/plain, */*', 'Accept-Language': 'en-US,en;q=0.5', 'X-Device-Info': f'{{"id":"{uuid.uuid4()}","hardware":{{"manufacturer":"Browser (Desktop)","model":"Firefox","version":"124.0"}},"os":{{"name":"Windows","version":"10"}},"display":{{"width":1920,"height":1080}},"legal":{{}}', } episode_metadata = self.session.get(url=title.data['playbackUrl'], headers=headers).json() tracks = DASH.from_url(episode_metadata['playbackInfo']['videoStreams'][1]['url']).to_tracks("en") title.data['licenseUrl'] = episode_metadata['playbackInfo']['videoStreams'][1]['drms'][0]['licenseUrl'] title.data['token'] = episode_metadata['playbackInfo']['videoStreams'][1]['drms'][0]['licenseTokenHeader']['value'] return tracks def get_chapters(self, title: Title_T) -> Chapters: return [] 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', 'Authorization': f'{title.data["token"]}', } return requests.post(url='https://multidrm.vsaas.verimatrixcloud.net/widevine', headers=headers, data=challenge).content