#!/usr/bin/python3
import sys
import os
import o11
import json
import datetime
import pytz
import jwt
import uuid
import secrets
from pywidevine.cdm import Cdm
from pywidevine.device import Device
from pywidevine.pssh import PSSH
from bs4 import BeautifulSoup

WVD_PATH = './WVD.wvd'

user = o11.parse_params(sys.argv, 'user')
password = o11.parse_params(sys.argv, 'password')

id = o11.parse_params(sys.argv, 'id')
action = o11.parse_params(sys.argv, 'action')

bind = o11.parse_params(sys.argv, 'bind')
proxy = o11.parse_params(sys.argv, 'proxy')
doh = o11.parse_params(sys.argv, 'doh')
worker = o11.parse_params(sys.argv, 'worker')

cdm_param = o11.parse_params(sys.argv, 'cdm')
challenge = o11.parse_params(sys.argv, 'challenge')
pin = o11.parse_params(sys.argv, 'pin')

o11Session = o11.session(bind=bind, proxy=proxy, worker=worker)
req = o11Session.get_session()
if doh != "":
    o11.dns(doh)

if challenge == "cert":
    challenge = "CAQ="

authFile = '/Dazn_' + user + '.tokens'
user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'

def get_version():
    headers = {'accept': '*/*', 'origin': 'https://www.dazn.com', 'referer': 'https://www.dazn.com/', 'user-agent': user_agent}
    response = req.get('https://pkg.fe.indazn.com/@dazn/peng-html5-core/live-production/web/it/0/version.json', headers=headers)
    return response.json().get('version', '4.53.8.24300')

def do_cdm_external(pssh_data, license_url, token):
    pssh_obj = PSSH(pssh_data)
    device_obj = Device.load(WVD_PATH)
    cdm_obj = Cdm.from_device(device_obj)
    session_id = cdm_obj.open()
    challenge_data = cdm_obj.get_license_challenge(session_id, pssh_obj)
    lic_headers = {'authorization': 'Bearer ' + token, 'content-type': 'application/octet-stream', 'origin': 'https://www.dazn.com', 'referer': 'https://www.dazn.com/', 'user-agent': user_agent, 'x-correlation-id': str(uuid.uuid4())}
    licence = req.post(license_url, headers=lic_headers, data=challenge_data)
    cdm_obj.parse_license(session_id, licence.content)
    keys = [f"{key.kid.hex}:{key.key.hex()}" for key in cdm_obj.get_keys(session_id) if key.type != 'SIGNING']
    cdm_obj.close(session_id)
    return keys

def get_pssh_from_mpd(url, cdn_token_name, cdn_token):
    headers = {'user-agent': user_agent, cdn_token_name: cdn_token}
    response = req.get(url, headers=headers)
    for cp in BeautifulSoup(response.content, features="xml").find_all('ContentProtection'):
        if cp.get('schemeIdUri', '').lower() == 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed':
            return cp.text.strip()
    return None

def get_single(token, asset_id, pin_code=None):
    decoded = jwt.decode(token, options={"verify_signature": False})
    version = get_version()
    headers = {'accept': '*/*', 'authorization': 'Bearer ' + token, 'origin': 'https://www.dazn.com', 'referer': 'https://www.dazn.com/', 'user-agent': user_agent, 'x-dazn-device': decoded['deviceId'], 'x-correlation-id': str(uuid.uuid4())}
    if pin_code:
        headers['x-age-verification-pin'] = pin_code
    params = {'AppVersion': version, 'DrmType': 'WIDEVINE', 'Format': 'MPEG-DASH', 'PlayerId': '@dazn/peng-html5-core/web/web', 'Platform': 'web', 'LanguageCode': 'en', 'Model': 'unknown', 'Secure': 'true', 'Manufacturer': 'microsoft', 'PlayReadyInitiator': 'false', 'Capabilities': 'mta', 'AssetId': asset_id, 'MtaLanguageCode': ''}
    response = req.get('https://api.playback.indazn.com/v5/Playback', params=params, headers=headers)
    data = response.json()
    playback_details = data.get('PlaybackDetails', [{}])[0]
    cdn_token_data = playback_details.get('CdnToken', {})
    return playback_details.get('ManifestUrl', ''), cdn_token_data.get('Name', ''), cdn_token_data.get('Value', ''), playback_details.get('LaUrl', ''), data.get('PlaybackLock', {}).get('LockId', '')

def delete_concurrency(token, lock_id):
    headers = {'authorization': 'Bearer ' + token, 'origin': 'https://www.dazn.com', 'referer': 'https://www.dazn.com/', 'user-agent': user_agent}
    req.delete('https://concurrency-v2.playback.indazn.com/v2/concurrency/lock/' + lock_id, headers=headers)

def get_rails(token):
    headers = {'accept': 'application/json, text/plain, */*', 'content-type': 'application/json', 'origin': 'https://www.dazn.com', 'referer': 'https://www.dazn.com/', 'user-agent': user_agent}
    decoded = jwt.decode(token, options={"verify_signature": False})
    entitlements = ','.join([e['id'] for e in decoded.get('entitlements', {}).get('entitlementSets', [])])
    params = {'groupId': 'home', 'country': decoded.get('contentCountry', ''), 'openBrowse': 'false', 'userEntitlements': entitlements}
    response = req.get('https://rails.discovery.indazn.com/eu/v8/rails', headers=headers, params=params)
    return response.json().get('Rails', [])

def single_rail(token, rail_id, rail_params):
    headers = {'accept': 'application/json, text/plain, */*', 'authorization': 'Bearer ' + token, 'content-type': 'application/json', 'origin': 'https://www.dazn.com', 'referer': 'https://www.dazn.com/', 'user-agent': user_agent}
    decoded = jwt.decode(token, options={"verify_signature": False})
    params = {'platform': 'web', 'id': rail_id, 'country': decoded.get('contentCountry', ''), 'languageCode': 'en', 'params': rail_params}
    response = req.get('https://rail-router.discovery.indazn.com/eu/v6/Rail', headers=headers, params=params)
    return response.json().get('Tiles', [])

def get_channels(token):
    channels = []
    rails = get_rails(token)
    for r in rails:
        tiles = single_rail(token, r['Id'], r.get('Params', ''))
        for t in tiles:
            if t.get('Type') == 'Live':
                channels.append(t)
    return list({c['Title']: c for c in channels}.values())

def do_login_request(username, pwd):
    headers = {'content-type': 'application/json', 'origin': 'https://www.dazn.com', 'referer': 'https://www.dazn.com/', 'user-agent': user_agent, 'x-dazn-ua': user_agent + 'signin/4.53.8.24300 hyper/0.14.0 (web; production; en)'}
    json_data = {'Email': username, 'Password': pwd, 'Platform': 'web', 'DeviceId': secrets.token_hex(5)}
    response = req.post('https://authentication-prod.ar.indazn.com/v5/SignIn', headers=headers, json=json_data)
    return response.json().get('AuthToken', {}).get('Token', '')

def do_refresh(token):
    headers = {'authorization': 'Bearer ' + token, 'content-type': 'application/json', 'origin': 'https://www.dazn.com', 'referer': 'https://www.dazn.com/', 'user-agent': user_agent}
    decoded = jwt.decode(token, options={"verify_signature": False})
    device_id = decoded.get('deviceId', '').split('-')[-1]
    json_data = {'DeviceId': device_id}
    response = req.post('https://ott-authz-bff-prod.ar.indazn.com/v5/RefreshAccessToken', headers=headers, json=json_data)
    return response.json().get('AuthToken', {}).get('Token', '')

def login():
    print("logging in...", file=sys.stderr)
    token = do_login_request(user, password)
    auth_data = {'access_token': token}
    json.dump(auth_data, open(os.path.abspath(os.path.dirname(__file__)) + authFile, 'w'))
    print("logged in successfully", file=sys.stderr)

def get_auth():
    auth = json.load(open(os.path.abspath(os.path.dirname(__file__)) + authFile))
    token = do_refresh(auth['access_token'])
    auth_data = {'access_token': token}
    json.dump(auth_data, open(os.path.abspath(os.path.dirname(__file__)) + authFile, 'w'))
    return token

def do_action():
    if action == "login":
        login()
        sys.exit()
    try:
        token = get_auth()
    except:
        return "error"

    if action == "channels":
        output = {'Channels': []}
        for c in get_channels(token):
            output['Channels'].append({'Name': c['Title'].replace('/', '-'), 'Mode': "live", 'SessionManifest': True, 'ManifestScript': 'asset=' + c['AssetId'], 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'asset=' + c['AssetId'], 'Video': 'best'})
        print(json.dumps(output, indent=2))
    elif action == "events":
        output = {'Events': []}
        for c in get_channels(token):
            output['Events'].append({'Name': c['Title'].replace('/', '-'), 'Mode': "live", 'SessionManifest': True, 'ManifestScript': 'asset=' + c['AssetId'], 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'asset=' + c['AssetId'], 'Video': 'best', 'Autostart': True, 'Start': int(datetime.datetime.now(pytz.UTC).timestamp()), 'End': int((datetime.datetime.now(pytz.UTC) + datetime.timedelta(hours=4)).timestamp())})
        print(json.dumps(output, indent=2))
    elif action == "manifest":
        asset_id = id.replace('asset=', '') if id.startswith('asset=') else id
        url, cdn_token_name, cdn_token, license_url, lock_id = get_single(token, asset_id, pin if pin else None)
        delete_concurrency(token, lock_id)
        pssh_data = get_pssh_from_mpd(url, cdn_token_name, cdn_token)
        output = {"Cdn": [], "ManifestUrl": url, "Headers": {"Manifest": {'User-Agent': user_agent, cdn_token_name: cdn_token}, "Media": {'User-Agent': user_agent, cdn_token_name: cdn_token}}, "LicenseUrl": license_url, "CdnTokenName": cdn_token_name, "CdnToken": cdn_token}
        if pssh_data:
            output['Pssh'] = pssh_data
        print(json.dumps(output))
    elif action == "cdm" and cdm_param == "external":
        asset_id = id.replace('asset=', '') if id.startswith('asset=') else id
        url, cdn_token_name, cdn_token, license_url, lock_id = get_single(token, asset_id, pin if pin else None)
        delete_concurrency(token, lock_id)
        pssh_data = get_pssh_from_mpd(url, cdn_token_name, cdn_token)
        if pssh_data and license_url:
            for key in do_cdm_external(pssh_data, license_url, token):
                print(key)

if do_action() == "error":
    login()
    do_action()
