#!/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 = '/DiscoveryPlus_' + user + '.tokens'
user_agent = 'Mozilla/5.0 (Linux; Android 9; SM-G955F Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.116 Mobile Safari/537.36'

def do_cdm_external(pssh_data, lic_url, drm_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': '*/*', 'origin': 'https://www.discoveryplus.com', 'preauthorization': drm_token, 'referer': 'https://www.discoveryplus.com/', 'user-agent': user_agent, 'content-type': 'application/x-www-form-urlencoded'}
    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 = {'Accept': '*/*', 'Origin': 'https://www.discoveryplus.com', 'Referer': 'https://www.discoveryplus.com/', 'User-Agent': user_agent}
    response = req.get(url, headers=headers)
    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(token):
    headers = {'x-disco-client': 'ANDROID:28:dplus_emea:17.55.0', 'x-disco-params': 'realm=dplay,features=ar;ppv,hth=gb,bid=dplus', 'Authorization': 'Bearer ' + token, 'user-agent': user_agent, 'Accept-Encoding': 'gzip'}
    response = req.get('https://eu1-prod.disco-api.com/cms/routes/home?include=default&decorators=viewingHistory%2CarticleBodyRichText.richTextHtml%2CisFavorite%2CplaybackAllowed%2CcontentAction%2Cbadges&page%5Bitems.size%5D=10&page%5Bitems.number%5D=1', headers=headers)
    r = []
    data = response.json()
    for i in data.get('included', []):
        if 'attributes' in i:
            attributes = i['attributes']
            if attributes.get('hasLiveStream'):
                r.append(i)
    return r

def get_single(token, content_id, event=False):
    headers = {'Authorization': 'Bearer ' + token, 'user-agent': user_agent, 'Content-Type': 'application/json; charset=UTF-8', 'Accept-Encoding': 'gzip'}
    json_data = {"deviceCapabilities": {"codecs": {"audio": {"decoders": [{"codec": "aac"}, {"codec": "eac3"}]}, "video": {"decoders": [{"codec": "h264", "levelConstraints": {"framerate": {"max": 960, "min": 0}, "height": {"max": 2160, "min": 32}, "width": {"max": 3840, "min": 32}}, "maxLevel": "5.2"}], "hdrFormats": []}}, "contentProtection": {"contentDecryptionModules": [{"drmKeySystem": "widevine", "maxSecurityLevel": "L3"}]}, "devicePlatform": {"network": {"capabilities": {"protocols": {"http": {"byteRangeRequests": True}}}, "lastKnownStatus": "wifi"}, "videoSink": {"lastKnownStatus": {"height": 2960, "width": 1440}}}, "manifest": {"formats": {"dash": {}}}, "segments": {"format": {"fmp4": {}}}}, "deviceInfo": {"adBlocker": False, "deviceId": "", "drmSupported": True, "hdrCapabilities": ["SDR"], "hwDecodingCapabilities": ["H264", "H265"], "player": {"height": 2960, "width": 1440}, "screen": {"height": 2960, "width": 1440}, "soundCapabilities": ["STEREO", "DOLBY_DIGITAL", "DOLBY_DIGITAL_PLUS"]}, "wisteriaProperties": {"product": "dplus_emea", "siteId": "dplus_uk", "gdpr": 1, "platform": "android", "appBundle": "com.discovery.dplay", "sessionId": "240f7562-8c3b-45fb-a87f-c983da29ee3b", "device": {"language": "en", "make": "samsung", "model": "SM-G955F", "os": "Android", "osVersion": "28", "type": "android/phone", "player": {"name": "Discovery Player Android googlePlay", "version": "0.13.0"}}, "streamProvider": {"suspendBeaconing": 1, "hlsVersion": 7, "pingConfig": 1, "version": "1.0.0"}, "config": {}}}
    if event:
        json_data['videoId'] = content_id
        url = 'https://eu1-prod.disco-api.com/playback/v3/videoPlaybackInfo'
    else:
        json_data['channelId'] = content_id
        url = 'https://eu1-prod.disco-api.com/playback/v3/channelPlaybackInfo'
    response = req.post(url, headers=headers, json=json_data)
    data = response.json()
    streaming = data['data']['attributes']['streaming'][0]
    return streaming['url'], streaming['protection']['schemes']['widevine']['licenseUrl'], streaming['protection']['drmToken']

def login():
    print("Please provide token manually in the tokens file", file=sys.stderr)
    print("Login to Discovery+ website, go to DevTools -> Application -> Cookies and copy 'st' value", file=sys.stderr)
    sys.exit(1)

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

    if action == "channels":
        output = {'Channels': []}
        for c in get_channels(token):
            output['Channels'].append({'Name': c['attributes']['name'], 'Mode': "live", 'SessionManifest': True, 'ManifestScript': 'id=' + c['id'] + '&type=' + c['type'], 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'id=' + c['id'] + '&type=' + c['type'], 'Video': 'best'})
        print(json.dumps(output, indent=2))
    elif action == "events":
        output = {'Events': []}
        for c in get_channels(token):
            output['Events'].append({'Name': c['attributes']['name'], 'Mode': "live", 'SessionManifest': True, 'ManifestScript': 'id=' + c['id'] + '&type=' + c['type'], 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'id=' + c['id'] + '&type=' + c['type'], '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('&')
        content_id = parts[0].replace('id=', '')
        content_type = parts[1].replace('type=', '') if len(parts) > 1 else 'channel'
        is_event = content_type == 'video'
        url, lic_url, drm_token = get_single(token, content_id, event=is_event)
        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, "DrmToken": drm_token}
        if pssh_data:
            output['Pssh'] = pssh_data
        print(json.dumps(output))
    elif action == "cdm" and cdm_param == "external":
        parts = id.split('&')
        content_id = parts[0].replace('id=', '')
        content_type = parts[1].replace('type=', '') if len(parts) > 1 else 'channel'
        is_event = content_type == 'video'
        url, lic_url, drm_token = get_single(token, content_id, event=is_event)
        pssh_data = get_pssh_from_mpd(url)
        if pssh_data:
            for key in do_cdm_external(pssh_data, lic_url, drm_token):
                print(key)

if do_action() == "error":
    print("Error: Please create token file with token", file=sys.stderr)
    sys.exit(1)
