#!/usr/bin/python3
import sys
import os
import o11
import json
import datetime
import pytz
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')

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

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

authFile = '/Kayo_' + user + '.tokens'
user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36'
device_id = '46af07d2-09f2-45ec-99bc-92d9ad067e90'

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 = {'accept': '*/*', 'authorization': 'Bearer ' + token, 'origin': 'https://kayosports.com.au', 'referer': 'https://kayosports.com.au/', 'user-agent': user_agent, 'content-type': 'application/x-www-form-urlencoded'}
    lic_url = 'https://drm.content.foxtel.com.au/licenseServer/widevine/v1/foxtelott/license?' + license_url.split('?')[-1]
    licence = req.post(lic_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):
    headers = {'User-Agent': user_agent}
    response = req.get(url, headers=headers)
    for cp in BeautifulSoup(response.content, features="xml").findAll('ContentProtection'):
        if cp.get('schemeIdUri') == 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed':
            pssh_tag = cp.find('cenc:pssh')
            if pssh_tag:
                return pssh_tag.text
    return None

def get_channels():
    headers = {'origin': 'https://kayosports.com.au', 'referer': 'https://kayosports.com.au/', 'user-agent': user_agent}
    response = req.get('https://api.kayosports.com.au/v3/panels/fcFQQs4Ulc2', headers=headers)
    return response.json().get('contents', [])

def get_single(token, vimond, asset_id):
    headers = {'accept': 'application/json', 'authorization': 'Bearer ' + token, 'content-type': 'application/json', 'origin': 'https://kayosports.com.au', 'referer': 'https://kayosports.com.au/', 'user-agent': user_agent, 'x-vimond-subprofile': vimond}
    json_data = {'assetId': asset_id, 'application': {'name': 'kayo', 'version': '21.1.0', 'appId': 'kayosports.com.au'}, 'device': {'id': device_id, 'type': 'desktop'}, 'os': {'name': 'Browser', 'version': user_agent}, 'browser': {'version': user_agent}, 'player': {'name': 'VideoFS', 'version': '20.1.0'}, 'capabilities': {'codecs': ['avc']}, 'ads': {'optOut': False}, 'session': {'intent': 'playback', 'startPos': -1}}
    response = req.post('https://play.kayosports.com.au/api/v3/play', headers=headers, json=json_data)
    data = response.json()
    for s in data.get('streams', []):
        if s.get('streamingFormat') == 'dash':
            return s.get('manifest', ''), s.get('licenseAcquisitionUrl', {}).get('com.widevine.alpha', '')
    return '', ''

def get_vimond(token, profile_id):
    headers = {'authorization': 'Bearer ' + token, 'origin': 'https://kayosports.com.au', 'referer': 'https://kayosports.com.au/', 'user-agent': user_agent}
    response = req.get(f'https://profileapi.streamotion.com.au/user/profile/{profile_id}', headers=headers)
    return response.json().get('vimond_token', '')

def get_profile_id(token):
    headers = {'accept': 'application/json', 'authorization': 'Bearer ' + token, 'origin': 'https://kayosports.com.au', 'referer': 'https://kayosports.com.au/', 'user-agent': user_agent}
    response = req.get('https://profileapi.streamotion.com.au/user/profile', headers=headers)
    data = response.json()
    return data[0]['id'] if data else ''

def refresh_token(refresh_tok):
    headers = {'auth0-client': 'eyJuYW1lIjoiYXV0aDAtc3BhLWpzIiwidmVyc2lvbiI6IjEuMjAuMSJ9', 'content-type': 'application/json', 'origin': 'https://kayosports.com.au', 'referer': 'https://kayosports.com.au/', 'user-agent': user_agent}
    json_data = {'client_id': '3sC2Dx5X1tTwtGlW8f6cN6YnkOuUIjcj', 'grant_type': 'refresh_token', 'refresh_token': refresh_tok, 'redirect_uri': 'https://kayosports.com.au/redirect'}
    response = req.post('https://auth.streamotion.com.au/oauth/token', headers=headers, json=json_data)
    data = response.json()
    return data.get('access_token', ''), data.get('refresh_token', '')

def login():
    print("logging in with refresh token...", file=sys.stderr)
    auth = json.load(open(os.path.abspath(os.path.dirname(__file__)) + authFile))
    token, new_refresh = refresh_token(auth['refresh_token'])
    profile_id = get_profile_id(token)
    auth_data = {'access_token': token, 'refresh_token': new_refresh, 'profile_id': profile_id}
    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))
    return auth['access_token'], auth['refresh_token'], auth['profile_id']

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

    if action == "channels":
        output = {'Channels': []}
        for c in get_channels():
            clickthrough = c.get('data', {}).get('clickthrough', {})
            output['Channels'].append({'Name': clickthrough.get('title', ''), 'Mode': "live", 'SessionManifest': True, 'ManifestScript': 'asset=' + str(clickthrough.get('asset', '')), 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'asset=' + str(clickthrough.get('asset', '')), 'Video': 'best'})
        print(json.dumps(output, indent=2))
    elif action == "events":
        output = {'Events': []}
        for c in get_channels():
            clickthrough = c.get('data', {}).get('clickthrough', {})
            output['Events'].append({'Name': clickthrough.get('title', ''), 'Mode': "live", 'SessionManifest': True, 'ManifestScript': 'asset=' + str(clickthrough.get('asset', '')), 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'asset=' + str(clickthrough.get('asset', '')), '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
        vimond = get_vimond(token, profile_id)
        url, license_url = get_single(token, vimond, asset_id)
        pssh_data = get_pssh_from_mpd(url)
        output = {"Cdn": [], "ManifestUrl": url, "Headers": {"Manifest": {'User-Agent': user_agent}, "Media": {'User-Agent': user_agent}}, "LicenseUrl": license_url}
        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
        vimond = get_vimond(token, profile_id)
        url, license_url = get_single(token, vimond, asset_id)
        pssh_data = get_pssh_from_mpd(url)
        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()
