#!/usr/bin/python3
import sys
import os
import o11
import json
import datetime
import pytz
import base64
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')
region = 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_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 = '/Allente_' + 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, token, stream_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)
    payload = base64.b64encode(challenge_data).decode()
    lic_headers = {'accept': '*/*', 'authorization': 'Bearer ' + token, 'content-type': 'application/json', 'origin': 'https://tv.allente.se', 'referer': 'https://tv.allente.se/', 'user-agent': user_agent, 'x-allente-devicetype': 'WEB'}
    data = json.dumps({'playerPayload': payload}).replace(' ', '')
    licence = req.post('https://w-sgprod-zulu.api-canaldigital.com/v1/drm/widevine/' + stream_id, headers=lic_headers, data=data)
    lic_data = licence.json()
    cdm_obj.parse_license(session_id, base64.b64decode(lic_data['license']))
    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': 'application/json, text/plain, */*', 'origin': 'https://tv.allente.se', 'referer': 'https://tv.allente.se/', 'user-agent': user_agent}
    response = req.get(url, headers=headers, verify=False)
    for cp in BeautifulSoup(response.content, features="xml").findAll('ContentProtection'):
        if cp.get('schemeIdUri', '').lower() == '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(entitlement_tag):
    headers = {'accept': 'application/json, text/plain, */*', 'origin': 'https://tv.allente.se', 'referer': 'https://tv.allente.se/', 'user-agent': user_agent}
    response = req.get('https://w-sgprod-zulu.api-canaldigital.com/v1/channels?streamType=DASH,MSS&entitlementTag=' + entitlement_tag, headers=headers)
    return response.json().get('channels', [])

def get_single(token, entitlement_tag, channel_id):
    headers = {'accept': '*/*', 'authorization': 'Bearer ' + token, 'content-type': 'application/json', 'origin': 'https://tv.allente.se', 'referer': 'https://tv.allente.se/', 'user-agent': user_agent, 'x-allente-clientversion': '4.13.0-0', 'x-allente-devicetype': 'WEB'}
    params = {'streamType': 'DASH', 'entitlementTag': entitlement_tag, 'widevineLevel': 'L3'}
    response = req.get(f'https://w-sgprod-zulu.api-canaldigital.com/v1/playout/channel/{channel_id}', params=params, headers=headers)
    return response.json().get('stream', {}).get('url', '')

def fetch_token(refresh_token):
    headers = {'accept': 'application/json, text/plain, */*', 'content-type': 'application/json;charset=UTF-8', 'origin': 'https://tv.allente.se', 'referer': 'https://tv.allente.se/', 'user-agent': user_agent}
    data = json.dumps({'clientId': 'go-web-cdse', 'refreshToken': refresh_token}).replace(' ', '')
    response = req.post('https://w-sgprod-zulu.api-canaldigital.com/v1/authentication/login', headers=headers, data=data, verify=False)
    resp_data = response.json()
    return resp_data['accessToken'], resp_data['refreshToken'], resp_data['entitlementTag']

def init_login(reg):
    headers = {'referer': 'https://tv.allente.no/', 'user-agent': user_agent}
    params = {'client_id': f'go-web-{reg}', 'scope': 'profile', 'response_type': 'code', 'redirect_uri': f'https://tv.allente.{reg.replace("cd", "")}/'}
    response = req.get('https://logon.allente.tv/sso/rest/v1/oauth/authorize', params=params, headers=headers, allow_redirects=False, verify=False)
    return response.cookies.get(reg, '')

def do_login_request(reg, cd, username, pwd):
    cookies = {reg: cd}
    headers = {'content-type': 'application/json; charset=UTF-8', 'origin': 'https://logon.allente.tv', 'referer': 'https://logon.allente.tv/static/sso/login-password', 'user-agent': user_agent}
    json_data = {'username': username, 'password': pwd, 'isRegistrationRequired': False}
    req.post(f'https://logon.allente.tv/sso/rest/v1/user/login/go-web-{reg}', cookies=cookies, headers=headers, json=json_data, verify=False)

def get_auth_code(reg, cd):
    cookies = {reg: cd}
    headers = {'referer': 'https://logon.allente.tv/static/sso/login-password', 'user-agent': user_agent}
    response = req.get(f'https://logon.allente.tv/sso/rest/v1/oauth/continue/go-web-{reg}', cookies=cookies, headers=headers, verify=False)
    return parse.parse_qs(parse.urlparse(response.url).query).get('code', [''])[0]

def authentication(reg, code):
    headers = {'content-type': 'application/json;charset=UTF-8', 'origin': 'https://tv.allente.no', 'referer': 'https://tv.allente.no/', 'user-agent': user_agent, 'x-allente-appvariant': 'ALLENTE', 'x-allente-clientversion': '4.3.0-0', 'x-allente-devicetype': 'WEB'}
    json_data = {'clientId': f'go-web-{reg}', 'authCode': code}
    response = req.post('https://w-sgprod-zulu.api-canaldigital.com/v1/authentication/login', headers=headers, json=json_data, verify=False)
    resp_data = response.json()
    return resp_data['accessToken'], resp_data['refreshToken'], resp_data['entitlementTag']

def login():
    print("logging in...", file=sys.stderr)
    reg = region if region else 'cdse'
    cd = init_login(reg)
    do_login_request(reg, cd, user, password)
    code = get_auth_code(reg, cd)
    token, refresh_token, entitlement_tag = authentication(reg, code)
    auth_data = {'refreshToken': refresh_token, 'entitlementTag': entitlement_tag, 'region': reg}
    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))
        token, refresh_token, entitlement_tag = fetch_token(auth['refreshToken'])
        auth['refreshToken'] = refresh_token
        auth['entitlementTag'] = entitlement_tag
        json.dump(auth, open(os.path.abspath(os.path.dirname(__file__)) + authFile, 'w'))
    except:
        return "error"

    if action == "channels":
        output = {'Channels': []}
        for c in get_channels(entitlement_tag):
            output['Channels'].append({'Name': c['name'], 'Mode': "live", 'SessionManifest': True, 'ManifestScript': 'id=' + c['id'] + '&streamId=' + c['streamId'], 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'id=' + c['id'] + '&streamId=' + c['streamId'], 'Video': 'best'})
        print(json.dumps(output, indent=2))
    elif action == "events":
        output = {'Events': []}
        for c in get_channels(entitlement_tag):
            output['Events'].append({'Name': c['name'], 'Mode': "live", 'SessionManifest': True, 'ManifestScript': 'id=' + c['id'] + '&streamId=' + c['streamId'], 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'id=' + c['id'] + '&streamId=' + c['streamId'], '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.split('&')
        channel_id = parts[0].replace('id=', '')
        stream_id = parts[1].replace('streamId=', '') if len(parts) > 1 else ''
        url = get_single(token, entitlement_tag, channel_id)
        pssh_data = get_pssh_from_mpd(url)
        output = {"Cdn": [], "ManifestUrl": url, "Headers": {"Manifest": {'User-Agent': user_agent}, "Media": {'User-Agent': user_agent}}, "StreamId": stream_id}
        if pssh_data:
            output['Pssh'] = pssh_data
        print(json.dumps(output))
    elif action == "cdm" and cdm_param == "external":
        parts = id.split('&')
        channel_id = parts[0].replace('id=', '')
        stream_id = parts[1].replace('streamId=', '') if len(parts) > 1 else ''
        url = get_single(token, entitlement_tag, channel_id)
        pssh_data = get_pssh_from_mpd(url)
        if pssh_data and stream_id:
            for key in do_cdm_external(pssh_data, token, stream_id):
                print(key)

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