#!/usr/bin/python3
import sys
import os
import o11
import base64
import json
import datetime
import pytz
import hashlib
from urllib.parse import urlencode, urlparse, urlunparse, parse_qs
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 = o11.parse_params(sys.argv, 'cdm')
drm = o11.parse_params(sys.argv, 'drm')
kid = o11.parse_params(sys.argv, 'kid')
pssh = o11.parse_params(sys.argv, 'pssh')
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 = '/SuperNova_' + user + '.tokens'
user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36'

def do_cdm_external(pssh_data):
    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': '*/*', 'Origin': 'https://supernovaott.rs', 'User-Agent': user_agent, 'Content-Type': 'application/octet-stream'}
    licence = req.post('https://supernovaott.rs/drm.php/widevine', 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 = {'Accept': '*/*', 'Origin': 'https://supernovaott.rs', 'Referer': 'https://supernovaott.rs/', 'User-Agent': user_agent}
    response = req.get(url, headers=headers)
    psshs = BeautifulSoup(response.content, features="xml").findAll('cenc:pssh')
    return psshs[0].text if psshs else None

def get_uuid():
    headers = {'Accept': 'application/json, text/plain, */*', 'Content-Type': 'application/json;charset=UTF-8', 'Origin': 'https://supernovaott.rs', 'User-Agent': user_agent}
    data = json.dumps({'application_publication_id': 'white_dwarf'})
    response = req.post('https://supernovaott.rs/client_api.php/config/identify/format/json', headers=headers, data=data)
    return response.json()['uuid']

def get_session_id(uuid_val):
    headers = {'Accept': 'application/json, text/plain, */*', 'Content-Type': 'application/json;charset=UTF-8', 'Origin': 'https://supernovaott.rs', 'User-Agent': user_agent}
    data = json.dumps({'application_publication_id': 'white_dwarf', 'uuid': uuid_val, 'screen_height': 1440, 'screen_width': 2560, 'os': 'Windows', 'os_version': '10', 'device_model_string_id': 'Chrome 119.0.0.0', 'application_version': '1.7.0'})
    response = req.put('https://supernovaott.rs/client_api.php/config/identify/format/json', headers=headers, data=data)
    return response.json()['session_id']

def do_login_request(username, pwd, session_id_val):
    headers = {'Accept': 'application/json, text/plain, */*', 'Origin': 'https://supernovaott.rs', 'User-Agent': user_agent}
    data = json.dumps({'username': username, 'password': pwd})
    response = req.post(f'https://supernovaott.rs/client_api.php/user/login/session_id/{session_id_val}/format/json', headers=headers, data=data)
    data = response.json()
    return data['new_uuid'], data['session_token'], data['secure_streaming_token']

def get_access_token(new_uuid, session_token):
    str8 = new_uuid + session_token
    digest = hashlib.sha1(str8.encode("UTF-8")).digest()
    sb = []
    for b10 in digest:
        for shift in [4, 0]:
            i14 = (b10 >> shift) & 15
            sb.append(chr(i14 + 48 if i14 <= 9 else i14 + 87))
    return ''.join(sb)

def get_streams(session_id_val, access_token):
    headers = {'Accept': 'application/json, text/plain, */*', 'Referer': 'https://supernovaott.rs/', 'User-Agent': user_agent}
    response = req.get(f'https://supernovaott.rs/client_api.php/channel/list/session_id/{session_id_val}/access_token/{access_token}/format/json', headers=headers)
    return response.json()

def remove_tracking(url):
    u = urlparse(url)
    query = parse_qs(u.query, keep_blank_values=True)
    for k in ['profile_id', 'application_installation_id', 'uuid', 'subscriber_id', 'vh', 'id', 'application_id', 'detected_delivery_method', 'playlist_template']:
        query.pop(k, None)
    query['video_id'] = '20207727'
    return urlunparse(u._replace(query=urlencode(query, True)))

def change_token(url, secure_streaming_token):
    u = urlparse(url)
    query = parse_qs(u.query, keep_blank_values=True)
    query['token'] = secure_streaming_token
    return urlunparse(u._replace(query=urlencode(query, True)))

def login():
    print("logging in...", file=sys.stderr)
    uuid_val = get_uuid()
    session_id_val = get_session_id(uuid_val)
    new_uuid, session_token, secure_streaming_token = do_login_request(user, password, session_id_val)
    access_token = get_access_token(new_uuid, session_token)
    auth_data = {'sessionId': session_id_val, 'accessToken': access_token, 'secureStreamingToken': secure_streaming_token}
    json.dump(auth_data, open(os.path.abspath(os.path.dirname(__file__)) + authFile, 'w'))
    print("logged in successfully", file=sys.stderr)

def do_action():
    if action == "login":
        login()
        sys.exit()
    try:
        auth = json.load(open(os.path.abspath(os.path.dirname(__file__)) + authFile))
        session_id_val = auth['sessionId']
        access_token = auth['accessToken']
        secure_streaming_token = auth['secureStreamingToken']
    except:
        return "error"

    if action == "channels":
        output = {'Channels': []}
        for s in get_streams(session_id_val, access_token):
            url = remove_tracking(s['url']) + '&profile_id=true'
            url = change_token(url, secure_streaming_token)
            output['Channels'].append({'Name': s['epg_channel_name'], 'Mode': "live", 'SessionManifest': False, 'ManifestScript': 'id=' + str(s.get('id', '')) + '&url=' + url, 'CdmType': "widevine", 'UseCdm': s.get('drm', {}).get('widevine_protection', False), 'Cdm': 'id=' + str(s.get('id', '')) + '&url=' + url, 'Video': 'best'})
        print(json.dumps(output, indent=2))
    elif action == "events":
        output = {'Events': []}
        for s in get_streams(session_id_val, access_token):
            url = remove_tracking(s['url']) + '&profile_id=true'
            url = change_token(url, secure_streaming_token)
            output['Events'].append({'Name': s['epg_channel_name'], 'Mode': "live", 'SessionManifest': False, 'ManifestScript': 'id=' + str(s.get('id', '')) + '&url=' + url, 'CdmType': "widevine", 'UseCdm': s.get('drm', {}).get('widevine_protection', False), 'Cdm': 'id=' + str(s.get('id', '')) + '&url=' + url, '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":
        url = o11.parse_params(sys.argv, 'url')
        if not url and '&url=' in id:
            url = id.split('&url=')[1]
        output = {"Cdn": [], "ManifestUrl": url, "Headers": {"Manifest": {'User-Agent': user_agent}, "Media": {'User-Agent': user_agent}}}
        pssh_data = get_pssh_from_mpd(url)
        if pssh_data:
            output['Pssh'] = pssh_data
        print(json.dumps(output))
    elif action == "cdm" and cdm == "internal":
        lic_headers = {'Content-Type': 'application/octet-stream'}
        response = req.post('https://supernovaott.rs/drm.php/widevine', headers=lic_headers, data=base64.b64decode(challenge))
        response_b64 = str(base64.b64encode(response.content), 'ascii')
        print(response_b64 if response_b64.startswith('CA') else response.text)
    elif action == "cdm" and cdm == "external":
        url = o11.parse_params(sys.argv, 'url')
        if not url and '&url=' in id:
            url = id.split('&url=')[1]
        pssh_data = get_pssh_from_mpd(url)
        if pssh_data:
            for key in do_cdm_external(pssh_data):
                print(key)

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