#!/usr/bin/python3
import sys
import os
import o11
import json
import datetime
import pytz
from urllib import parse
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 = '/SunriseTV_' + 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 do_cdm_external(pssh_data, cookies, streaming_token, channel_id):
    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 = {'authority': 'spark-prod-ch.gnp.cloud.sunrisetv.ch', 'origin': 'https://www.sunrisetv.ch', 'referer': 'https://www.sunrisetv.ch/', 'user-agent': user_agent, 'x-streaming-token': streaming_token, 'x-drm-schemeid': 'edef8ba9-79d6-4ace-a3c8-27dcd51d21ed', 'content-type': 'application/octet-stream'}
    params = {'ContentId': channel_id}
    licence = req.post('https://spark-prod-ch.gnp.cloud.sunrisetv.ch/eng/web/session-manager/license', headers=lic_headers, data=challenge_data, cookies=cookies, params=params)
    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 = {'Origin': 'https://www.sunrisetv.ch', 'Referer': 'https://www.sunrisetv.ch/', '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, response.url
    return None, response.url

def get_channels():
    headers = {'authority': 'spark-prod-ch.gnp.cloud.sunrisetv.ch', 'origin': 'https://www.sunrisetv.ch', 'referer': 'https://www.sunrisetv.ch/', 'user-agent': user_agent}
    params = {'cityId': '300', 'language': 'en', 'productClass': 'Orion-DASH'}
    response = req.get('https://spark-prod-ch.gnp.cloud.sunrisetv.ch/eng/web/linear-service/v2/channels', params=params, headers=headers)
    return response.json()

def get_streaming_token(cookies, household_id, channel_id):
    headers = {'origin': 'https://www.sunrisetv.ch', 'referer': 'https://www.sunrisetv.ch/', 'user-agent': user_agent}
    params = {'channelId': channel_id, 'assetType': 'Orion-DASH'}
    response = req.post(f'https://spark-prod-ch.gnp.cloud.sunrisetv.ch/eng/web/session-service/session/v2/web-desktop/customers/{household_id}/live', params=params, cookies=cookies, headers=headers)
    return response.headers.get('X-Streaming-Token', '')

def delete_session(cookies, streaming_token):
    headers = {'authority': 'spark-prod-ch.gnp.cloud.sunrisetv.ch', 'origin': 'https://www.sunrisetv.ch', 'referer': 'https://www.sunrisetv.ch/', 'user-agent': user_agent, 'x-streaming-token': streaming_token}
    req.delete('https://spark-prod-ch.gnp.cloud.sunrisetv.ch/eng/web/session-manager/license/token', cookies=cookies, headers=headers)

def do_refresh(refresh_token):
    headers = {'authority': 'spark-prod-ch.gnp.cloud.sunrisetv.ch', 'content-type': 'application/json; charset=utf-8', 'origin': 'https://www.sunrisetv.ch', 'referer': 'https://www.sunrisetv.ch/', 'user-agent': user_agent}
    json_data = {'refreshToken': refresh_token}
    response = req.post('https://spark-prod-ch.gnp.cloud.sunrisetv.ch/auth-service/v1/authorization/refresh', headers=headers, json=json_data)
    data = response.json()
    return response.cookies.get_dict(), data.get('refreshToken', ''), data.get('householdId', '')

def init_login(code_challenge):
    headers = {'origin': 'https://www.sunrisetv.ch', 'referer': 'https://www.sunrisetv.ch/', 'user-agent': user_agent, 'x-cvp': 'upc_ch', 'x-device-code': 'web'}
    params = {'code_challenge': code_challenge, 'language': 'de'}
    response = req.get('https://spark-prod-ch.gnp.cloud.sunrisetv.ch/auth-service/v1/sso/authorization', params=params, headers=headers)
    data = response.json()
    return data.get('authorizationUri', ''), data.get('validityToken', '')

def get_login_form(url):
    headers = {'Content-Type': 'application/x-www-form-urlencoded', 'User-Agent': user_agent}
    response = req.get(url, headers=headers)
    soup = BeautifulSoup(response.content, features='lxml')
    return soup.find("form", {"id": "kc-form-login"})['action']

def do_login_request(url, username, pwd):
    headers = {'Content-Type': 'application/x-www-form-urlencoded', 'User-Agent': user_agent}
    data = {'username': username, 'password': pwd}
    response = req.post(url, headers=headers, data=data)
    return parse.parse_qs(parse.urlparse(response.url).query).get('code', [''])[0]

def get_tokens(code, code_verifier, validity_token):
    headers = {'content-type': 'application/json; charset=utf-8', 'origin': 'https://www.sunrisetv.ch', 'referer': 'https://www.sunrisetv.ch/', 'user-agent': user_agent, 'x-cvp': 'upc_ch', 'x-device-code': 'web'}
    params = {'loginAction': 'user'}
    json_data = {'authorizationGrant': {'authorizationCode': code, 'codeVerifier': code_verifier, 'validityToken': validity_token}}
    response = req.post('https://spark-prod-ch.gnp.cloud.sunrisetv.ch/auth-service/v1/sso/authorization', params=params, headers=headers, json=json_data)
    data = response.json()
    return data.get('refreshToken', ''), data.get('householdId', ''), response.cookies.get_dict()

def login():
    import pkce
    print("logging in...", file=sys.stderr)
    code_verifier, code_challenge = pkce.generate_pkce_pair()
    auth_url, validity_token = init_login(code_challenge)
    login_url = get_login_form(auth_url)
    code = do_login_request(login_url, user, password)
    refresh_token, household_id, cookies = get_tokens(code, code_verifier, validity_token)
    auth_data = {'refresh_token': refresh_token, 'household_id': household_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))
    cookies, refresh_token, household_id = do_refresh(auth['refresh_token'])
    auth['refresh_token'] = refresh_token
    auth['household_id'] = household_id
    json.dump(auth, open(os.path.abspath(os.path.dirname(__file__)) + authFile, 'w'))
    return cookies, household_id

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

    if action == "channels":
        output = {'Channels': []}
        for c in get_channels():
            output['Channels'].append({'Name': c['name'], 'Mode': "live", 'SessionManifest': True, 'ManifestScript': 'cid=' + c['id'] + '&loc=' + c.get('locator', ''), 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'cid=' + c['id'] + '&loc=' + c.get('locator', ''), 'Video': 'best'})
        print(json.dumps(output, indent=2))
    elif action == "events":
        output = {'Events': []}
        for c in get_channels():
            output['Events'].append({'Name': c['name'], 'Mode': "live", 'SessionManifest': True, 'ManifestScript': 'cid=' + c['id'] + '&loc=' + c.get('locator', ''), 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'cid=' + c['id'] + '&loc=' + c.get('locator', ''), '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":
        parts = id.replace('cid=', '').split('&loc=')
        channel_id = parts[0]
        locator = parts[1] if len(parts) > 1 else ''
        streaming_token = get_streaming_token(cookies, household_id, channel_id)
        url = locator.replace('/dash/', f'/dash,vxttoken={streaming_token}/')
        pssh_data, final_url = get_pssh_from_mpd(url)
        delete_session(cookies, streaming_token)
        output = {"Cdn": [], "ManifestUrl": final_url, "Headers": {"Manifest": {'User-Agent': user_agent}, "Media": {'User-Agent': user_agent}}, "StreamingToken": streaming_token, "ChannelId": channel_id}
        if pssh_data:
            output['Pssh'] = pssh_data
        print(json.dumps(output))
    elif action == "cdm" and cdm_param == "external":
        parts = id.replace('cid=', '').split('&loc=')
        channel_id = parts[0]
        locator = parts[1] if len(parts) > 1 else ''
        streaming_token = get_streaming_token(cookies, household_id, channel_id)
        url = locator.replace('/dash/', f'/dash,vxttoken={streaming_token}/')
        pssh_data, final_url = get_pssh_from_mpd(url)
        if pssh_data:
            for key in do_cdm_external(pssh_data, cookies, streaming_token, channel_id):
                print(key)
        delete_session(cookies, streaming_token)

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