139 lines
5.4 KiB
Python
139 lines
5.4 KiB
Python
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 |