#!/usr/bin/python3
import sys
import os
import o11
import base64
import json
import datetime
import pytz
import jwt
from urllib.parse import unquote
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')
persistent_login = o11.parse_params(sys.argv, 'persistentLogin')
region_param = o11.parse_params(sys.argv, 'region')

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')
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 = '/Viaplay_' + user + '.tokens'
user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36'

def do_cdm_external(pssh_data, lic_url):
    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 = {'Origin': 'https://viaplay.com', 'Referer': 'https://viaplay.se/', 'User-Agent': user_agent, 'Content-Type': 'application/octet-stream'}
    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 find_wv_pssh_offsets(raw):
    offsets = []
    offset = 0
    while True:
        offset = raw.find(b'pssh', offset)
        if offset == -1:
            break
        size = int.from_bytes(raw[offset-4:offset], byteorder='big')
        offsets.append(raw[offset-4:offset-4+size])
        offset += size
    return offsets

def get_pssh_from_mpd(url):
    headers = {'accept': '*/*', 'user-agent': user_agent}
    response = req.get(url, headers=headers)
    soup = BeautifulSoup(response.content, features="xml")
    for cp in soup.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
    init = soup.find('SegmentTemplate')
    if init and init.get('initialization'):
        bandwidth = soup.find('Representation')['bandwidth']
        rep_id = soup.find('Representation')['id']
        base_url = soup.find('BaseURL')
        loc_parts = response.url.split('/')[:-1]
        if base_url and 'http' in base_url.text:
            init_url = base_url.text + init['initialization'].replace('$Bandwidth$', bandwidth).replace('$RepresentationID$', rep_id)
        else:
            init_url = '/'.join(loc_parts) + '/' + init['initialization'].replace('$Bandwidth$', bandwidth).replace('$RepresentationID$', rep_id)
        resp = req.get(init_url, headers=headers)
        psshs = [base64.b64encode(wv).decode() for wv in find_wv_pssh_offsets(resp.content)]
        return psshs[-1] if psshs else None
    return None

def get_endpoints(region):
    endpoints = {
        'SE': ('https://content.viaplay.se/pcdash-se/tv', 'https://content.viaplay.se/pcdash-se/sport'),
        'NO': ('https://content.viaplay.no/pcdash-no/kanaler', 'https://content.viaplay.no/pcdash-no/sport'),
        'DK': (None, 'https://content.viaplay.dk/pcdash-dk/sport'),
        'PL': ('https://content.viaplay.pl/pcdash-pl/channels', 'https://content.viaplay.pl/pcdash-pl/sport'),
        'NL': (None, 'https://content.viaplay.com/pcdash-nl/sport'),
    }
    return endpoints.get(region, (None, None))

def refresh_cookies(persistent_login_val):
    cookies = {'persistentLogin': persistent_login_val}
    headers = {'Accept': '*/*', 'Origin': 'https://viaplay.com', 'Referer': 'https://viaplay.com/', 'User-Agent': user_agent}
    params = {'deviceKey': 'pcdash-nl', 'returnurl': 'https://content.viaplay.se/pcdash-se/tv'}
    response = req.get('https://login.viaplay.com/api/persistentLogin/v1', params=params, cookies=cookies, headers=headers)
    return response.cookies.get_dict()

def get_channels(cookies, channel_url, event_url):
    headers = {'origin': 'https://viaplay.com', 'referer': 'https://viaplay.com/', 'user-agent': user_agent}
    r = []
    if channel_url:
        response = req.get(channel_url, cookies=cookies, headers=headers)
        data = response.json()
        r += data['_embedded']['viaplay:blocks'][0]['_embedded'].get('viaplay:blocks', [])
    if event_url:
        response = req.get(event_url, cookies=cookies, headers=headers)
        data = response.json()
        for b in data['_embedded'].get('viaplay:blocks', []):
            if 'live' in b.get('title', '').lower():
                r += b['_embedded'].get('viaplay:products', [])
    return r

def get_single(cookies, stream_url):
    headers = {'Origin': 'https://viaplay.com', 'Referer': 'https://viaplay.com/', 'User-Agent': user_agent}
    url = stream_url.split('{')[0] + '?' + stream_url.split('}')[-1][1:]
    params = {'deviceId': '0baf9c97-6407-4b7a-9841-bfbf58350a9a', 'deviceName': 'web', 'deviceType': 'pc', 'userAgent': user_agent}
    response = req.get(url, params=params, cookies=cookies, headers=headers)
    data = response.json()
    return data['_links']['viaplay:media']['href'].split('?')[0], data['_links']['viaplay:widevineLicense']['href']

def login():
    print("Please provide persistentLogin cookie from browser DevTools", file=sys.stderr)
    if persistent_login:
        pers = json.loads(unquote(persistent_login).replace('j:', ''))
        decoded = jwt.decode(pers['token'], options={"verify_signature": False})
        auth_data = {'persistentLogin': persistent_login, 'region': decoded.get('ctry', 'SE')}
        json.dump(auth_data, open(os.path.abspath(os.path.dirname(__file__)) + authFile, 'w'))
        print("Token saved 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))
        cookies = refresh_cookies(auth['persistentLogin'])
        region = auth.get('region', 'SE')
        channel_url, event_url = get_endpoints(region)
    except:
        return "error"

    if action == "channels":
        output = {'Channels': []}
        for c in get_channels(cookies, channel_url, event_url):
            title = c.get('viaplay:channel', {}).get('content', {}).get('title') or c.get('content', {}).get('title', '')
            stream_url = c.get('viaplay:channel', {}).get('_embedded', {}).get('viaplay:products', [{}])[0].get('_links', {}).get('viaplay:stream', {}).get('href') or c.get('_links', {}).get('viaplay:stream', {}).get('href', '')
            if title and stream_url:
                output['Channels'].append({'Name': title, 'Mode': "live", 'SessionManifest': True, 'ManifestScript': 'streamUrl=' + stream_url, 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'streamUrl=' + stream_url, 'Video': 'best'})
        print(json.dumps(output, indent=2))
    elif action == "events":
        output = {'Events': []}
        for c in get_channels(cookies, channel_url, event_url):
            title = c.get('viaplay:channel', {}).get('content', {}).get('title') or c.get('content', {}).get('title', '')
            stream_url = c.get('viaplay:channel', {}).get('_embedded', {}).get('viaplay:products', [{}])[0].get('_links', {}).get('viaplay:stream', {}).get('href') or c.get('_links', {}).get('viaplay:stream', {}).get('href', '')
            if title and stream_url:
                output['Events'].append({'Name': title, 'Mode': "live", 'SessionManifest': True, 'ManifestScript': 'streamUrl=' + stream_url, 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'streamUrl=' + stream_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":
        stream_url = id.replace('streamUrl=', '') if id.startswith('streamUrl=') else id
        url, lic_url = get_single(cookies, stream_url)
        pssh_data = get_pssh_from_mpd(url)
        output = {"Cdn": [], "ManifestUrl": url, "Headers": {"Manifest": {'User-Agent': user_agent}, "Media": {'User-Agent': user_agent}}, "LicenseUrl": lic_url}
        if pssh_data:
            output['Pssh'] = pssh_data
        print(json.dumps(output))
    elif action == "cdm" and cdm == "external":
        stream_url = id.replace('streamUrl=', '') if id.startswith('streamUrl=') else id
        url, lic_url = get_single(cookies, stream_url)
        pssh_data = get_pssh_from_mpd(url)
        if pssh_data:
            for key in do_cdm_external(pssh_data, lic_url):
                print(key)

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