#!/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 = '/RTL_' + 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, 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 = {'origin': 'https://plus.rtl.de', 'referer': 'https://plus.rtl.de/', 'user-agent': user_agent, 'x-auth-token': token, '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 get_pssh_from_mpd(url):
    headers = {'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 = {'authorization': 'Bearer ' + token, 'origin': 'https://plus.rtl.de', 'referer': 'https://plus.rtl.de/', 'rtlplus-client-id': 'rci:rtlplus:web', 'rtlplus-client-version': '2024.2.23.2', 'user-agent': user_agent}
    params = {'operationName': 'LiveTvStations', 'variables': '{"epgCount":4,"filter":{"channelTypes":["BROADCAST","FAST"]}}', 'extensions': '{"persistedQuery":{"version":1,"sha256Hash":"845cf56a2a78110a0f978c1a2af2bc7f9a1c937d0f324ffaf852a9a4414c8485"}}'}
    response = req.get('https://cdn.gateway.now-plus-prod.aws-cbc.cloud/graphql', headers=headers, params=params)
    return response.json().get('data', {}).get('liveTvStations', [])

def get_single(token, channel_id):
    headers = {'accept': 'application/json, text/plain, */*', 'authorization': 'Bearer ' + token, 'origin': 'https://plus.rtl.de', 'pragma': 'no-cache', 'referer': 'https://plus.rtl.de/', 'rtlplus-client-id': 'rci:rtlplus:web', 'rtlplus-client-version': '2024.2.22.3', 'user-agent': user_agent}
    params = {'operationName': 'WatchPlayerConfigV3', 'variables': '{"platform":"WEB","id":"' + channel_id + '"}', 'extensions': '{"persistedQuery":{"version":1,"sha256Hash":"fea0311fb572b6fded60c5a1a9d652f97f55d182bc4cedbdad676354a8d2797c"}}'}
    response = req.get('https://cdn.gateway.now-plus-prod.aws-cbc.cloud/graphql', headers=headers, params=params)
    data = response.json()
    playout_variants = data.get('data', {}).get('watchPlayerConfigV3', {}).get('playoutVariants', [])
    url, lic_url = None, None
    for p in playout_variants:
        if p.get('type') == 'dashhd':
            url = p.get('sources', [{}])[0].get('url', '').split('?')[0]
            for l in p.get('licenses', []):
                if l.get('type') == 'WIDEVINE':
                    lic_url = l.get('licenseUrl', '')
    return url, lic_url

def do_refresh(refresh_token):
    headers = {'content-type': 'application/x-www-form-urlencoded', 'origin': 'https://plus.rtl.de', 'referer': 'https://plus.rtl.de/', 'user-agent': user_agent}
    data = {'grant_type': 'refresh_token', 'refresh_token': refresh_token, 'client_id': 'rtlplus-web'}
    response = req.post('https://auth.rtl.de/auth/realms/rtlplus/protocol/openid-connect/token?ngsw-bypass', headers=headers, data=data)
    resp_data = response.json()
    return resp_data.get('access_token', ''), resp_data.get('refresh_token', '')

def init_login(code_challenge):
    headers = {'User-Agent': user_agent, 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'Referer': 'https://plus.rtl.de/'}
    params = {'client_id': 'rtlplus-web', 'redirect_uri': 'https://plus.rtl.de/', 'response_mode': 'query', 'response_type': 'code', 'scope': 'openid email', 'prompt': 'login', 'code_challenge': code_challenge, 'code_challenge_method': 'S256'}
    response = req.get('https://auth.rtl.de/auth/realms/rtlplus/protocol/openid-connect/auth', headers=headers, params=params)
    soup = BeautifulSoup(response.content, features='lxml')
    return soup.find('form')['action']

def do_login_request(execution, username, pwd):
    headers = {'User-Agent': user_agent, 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'Referer': 'https://plus.rtl.de/'}
    data = {'credentialId': '', 'rememberMe': 'on', 'username': username, 'password': pwd}
    response = req.post(execution, headers=headers, data=data)
    return parse.parse_qs(parse.urlparse(response.url).query).get('code', [''])[0]

def final_login(code, code_verifier):
    headers = {'User-Agent': user_agent, 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'Referer': 'https://plus.rtl.de/'}
    data = {'code': code, 'grant_type': 'authorization_code', 'client_id': 'rtlplus-web', 'redirect_uri': 'https://plus.rtl.de/', 'code_verifier': code_verifier}
    response = req.post('https://auth.rtl.de/auth/realms/rtlplus/protocol/openid-connect/token?ngsw-bypass', headers=headers, data=data)
    resp_data = response.json()
    return resp_data.get('access_token', ''), resp_data.get('refresh_token', '')

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

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

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

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