#!/usr/bin/python3
import sys
import os
import o11
import json
import datetime
import pytz
import pkce
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 = '/RTLPlus_' + 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_events(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': 'LiveEventsOverviewPage', 'variables': '{}', 'extensions': '{"persistedQuery":{"version":1,"sha256Hash":"77a8f26d5de76daf801fcd7ae54bebd0ecabbeeb3ecb90889bc4a2e5590b7d20"}}'}
    response = req.get('https://cdn.gateway.now-plus-prod.aws-cbc.cloud/graphql', headers=headers, params=params)
    timezone = pytz.timezone('Europe/Paris')
    r = []
    data = response.json()
    for t in data.get('data', {}).get('liveEventsOverview', {}).get('teaserRows', []):
        for e in t.get('events', []):
            start_timestamp = datetime.datetime.strptime(e['streamStart'], "%Y-%m-%dT%H:%M:%S.%f%z").astimezone(timezone)
            end_timestamp = datetime.datetime.strptime(e['streamEnd'], "%Y-%m-%dT%H:%M:%S.%f%z").astimezone(timezone)
            current_time = datetime.datetime.now(timezone)
            if start_timestamp <= current_time <= end_timestamp:
                r.append({'name': e['title'], 'id': e['id'], 'isEvent': True})
    return r

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()
    url, lic_url = '', ''
    for p in data.get('data', {}).get('watchPlayerConfigV3', {}).get('playoutVariants', []):
        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 get_event(token, event_id):
    headers = {'accept': 'application/json', 'content-type': 'application/json', 'origin': 'https://plus.rtl.de', 'referer': 'https://plus.rtl.de/', 'user-agent': user_agent, 'x-auth-token': token}
    params = {'platform': 'web'}
    response = req.get('https://stus.player.streamingtech.de/liveevent/' + event_id, params=params, headers=headers)
    data = response.json()
    url, lic_url = '', ''
    for p in data:
        if p.get('name') == 'dashhd':
            url = p.get('sources', [{}])[0].get('url', '').split('?')[0]
            for l in p.get('licenses', []):
                if l.get('variant') == 'WIDEVINELICENSE':
                    lic_url = l.get('uri', {}).get('href', '')
    return url, lic_url

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')
    form = soup.find('form')
    return form['action'] if form else ''

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)
    data = response.json()
    return data.get('access_token', ''), data.get('refresh_token', '')

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)
    data = response.json()
    return data.get('access_token', ''), data.get('refresh_token', '')

def login():
    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_token():
    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_token()
    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': 'id=' + c['id'], 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'id=' + 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': 'id=' + c['id'], 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'id=' + 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 == "liveevents":
        output = {'Events': []}
        for c in get_events(token):
            output['Events'].append({'Name': c['name'], 'Mode': "live", 'SessionManifest': True, 'ManifestScript': 'event=' + c['id'], 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'event=' + 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":
        if id.startswith('event='):
            event_id = id.replace('event=', '')
            url, lic_url = get_event(token, event_id)
        else:
            channel_id = id.replace('id=', '') if id.startswith('id=') else id
            url, lic_url = get_single(token, channel_id)
        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, "Token": token}
        if pssh_data:
            output['Pssh'] = pssh_data
        print(json.dumps(output))
    elif action == "cdm" and cdm_param == "external":
        if id.startswith('event='):
            event_id = id.replace('event=', '')
            url, lic_url = get_event(token, event_id)
        else:
            channel_id = id.replace('id=', '') if id.startswith('id=') else id
            url, lic_url = get_single(token, channel_id)
        pssh_data = get_pssh_from_mpd(url)
        if pssh_data and lic_url:
            for key in do_cdm_external(pssh_data, lic_url, token):
                print(key)

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