#!/usr/bin/python3
import sys
import os
import o11
import json
import datetime
import pytz
import base64
import uuid
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 = '/Magenta_' + 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, basic_auth, pid):
    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)
    token_parts = base64.b64decode(basic_auth.encode()).decode().split(':')
    account = token_parts[0] + ':' + token_parts[1]
    token = token_parts[-1]
    lic_headers = {'Origin': 'https://web2.magentatv.de', 'Referer': 'https://web2.magentatv.de/', 'User-Agent': user_agent, 'Content-Type': 'application/octet-stream'}
    params = {'account': account, 'releasePid': pid, 'token': token, 'schema': '1.0'}
    licence = req.post('https://widevine.entitlement.theplatform.eu/wv/web/ModularDrm/getRawWidevineLicense', headers=lic_headers, data=challenge_data, params=params)
    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')
        pssh_offset = offset - 4
        offsets.append(raw[pssh_offset:pssh_offset+size])
        offset += size
    return offsets

def to_pssh(content):
    wv_offsets = find_wv_pssh_offsets(content)
    return [base64.b64encode(wv_offset).decode() for wv_offset in wv_offsets]

def init_to_pssh(init_url):
    headers = {'accept': '*/*', 'user-agent': user_agent, 'content-type': 'application/octet-stream'}
    response = req.get(init_url, headers=headers)
    return to_pssh(response.content)

def get_pssh_from_mpd(url):
    headers = {'accept': '*/*', 'user-agent': user_agent, 'content-type': 'application/octet-stream'}
    response = req.get(url, headers=headers)
    location = response.url
    soup = BeautifulSoup(response.content, features="xml")
    init = soup.find('SegmentTemplate')['initialization']
    bandwidth = soup.find('Representation')['bandwidth']
    rep_id = soup.find('Representation')['id']
    loc_parts = location.split('/')
    loc_parts.pop()
    init_url = '/'.join(loc_parts) + '/' + init.replace('$Bandwidth$', bandwidth).replace('$RepresentationID$', rep_id)
    psshs = init_to_pssh(init_url)
    return psshs[0] if psshs else None

def get_channels():
    headers = {'Origin': 'https://web2.magentatv.de', 'Referer': 'https://web2.magentatv.de/', 'User-Agent': user_agent}
    params = {'lang': 'short-de', 'sort': 'dt$displayChannelNumber', 'range': '1-999'}
    response = req.get('https://feed.entertainment.tv.theplatform.eu/f/mdeprod/mdeprod-channel-stations-main', params=params, headers=headers)
    data = response.json()
    r = []
    for e in data.get('entries', []):
        stations = e.get('stations', {})
        for k, v in stations.items():
            if v.get('isHd', False):
                r.append(v)
    return r

def get_single(basic_auth, media_id):
    headers = {'Authorization': 'Basic ' + basic_auth, 'Origin': 'https://web2.magentatv.de', 'Referer': 'https://web2.magentatv.de/', 'User-Agent': user_agent}
    params = {'format': 'SMIL', 'formats': 'MPEG-DASH', 'tracking': 'true'}
    response = req.get('https://link.theplatform.eu/s/mdeprod/media/' + media_id, params=params, headers=headers)
    soup = BeautifulSoup(response.content, features='lxml')
    url = soup.find("video")['src']
    tracking_data = soup.find("param", {"name": "trackingData"})['value'].split('|')
    pid = ''
    for t in tracking_data:
        if 'pid' in t:
            pid = t.split('=')[-1]
    lock = soup.find("meta", {"name": "lock"})['content']
    lock_id = soup.find("meta", {"name": "lockId"})['content']
    lock_token = soup.find("meta", {"name": "lockSequenceToken"})['content']
    return url, pid, lock, lock_id, lock_token

def unlock(device_id, lock, lock_id, lock_token):
    headers = {'Origin': 'https://web2.magentatv.de', 'Referer': 'https://web2.magentatv.de/', 'User-Agent': user_agent}
    params = {'_clientId': device_id, '_encryptedLock': lock, '_id': lock_id, '_sequenceToken': lock_token, 'form': 'json', 'schema': '1.0'}
    req.get('https://concurrency.delivery.theplatform.eu/concurrency/web/Concurrency/unlock', params=params, headers=headers)

def get_login_url(device_id, session_id):
    headers = {'User-Agent': user_agent, 'Referer': 'https://web2.magentatv.de/', 'device-id': device_id, 'session-id': session_id, 'Origin': 'https://web2.magentatv.de'}
    response = req.get('https://ssom.magentatv.de/login', headers=headers)
    return response.json().get('loginRedirectUrl', '')

def get_login_values(url):
    headers = {'User-Agent': user_agent, 'Referer': 'https://web2.magentatv.de/', 'Origin': 'https://web2.magentatv.de'}
    response = req.get(url, headers=headers)
    soup = BeautifulSoup(response.content, features='lxml')
    xsrf_name, xsrf_value = '', ''
    for i in soup.findAll('input'):
        if i.get('name', '').startswith('xsrf_'):
            xsrf_name = i['name']
            xsrf_value = i['value']
    tid = soup.find("input", {"name": "tid"})['value']
    return xsrf_name, xsrf_value, tid

def do_login_request(login_url, xsrf_name, xsrf_value, tid, username, pwd):
    headers = {'User-Agent': user_agent, 'Content-Type': 'application/x-www-form-urlencoded', 'Origin': 'https://accounts.login.idm.telekom.com', 'Referer': login_url}
    data = {xsrf_name: xsrf_value, 'tid': tid, 'x-show-cancel': 'false', 'bdata': '', 'pkc': '', 'webauthn_supported': 'true', 'pw_usr': username, 'pw_submit': '', 'hidden_pwd': ''}
    response = req.post('https://accounts.login.idm.telekom.com/factorx', headers=headers, data=data)
    soup = BeautifulSoup(response.content, features='lxml')
    for i in soup.findAll('input'):
        if i.get('name', '').startswith('xsrf_'):
            xsrf_name = i['name']
            xsrf_value = i['value']
    data = {xsrf_name: xsrf_value, 'tid': tid, 'hidden_usr': username, 'bdata': '', 'pw_pwd': pwd, 'persist_session_displayed': '1', 'pw_submit': ''}
    response = req.post('https://accounts.login.idm.telekom.com/factorx', headers=headers, data=data)
    parsed_url = parse.parse_qs(parse.urlparse(response.url).query)
    return parsed_url['code'][0], parsed_url['state'][0]

def authenticate(device_id, session_id, code=None, state=None):
    headers = {'content-type': 'application/json', 'device-id': device_id, 'origin': 'https://web2.magentatv.de', 'referer': 'https://web2.magentatv.de/', 'session-id': session_id, 'user-agent': user_agent}
    json_data = {'checkRefreshToken': True}
    if code and state:
        json_data['returnCode'] = {'code': code, 'state': state}
    response = req.post('https://ssom.magentatv.de/authenticate', headers=headers, json=json_data)
    return response.json().get('userInfo', {}).get('personaToken', '')

def login():
    print("logging in...", file=sys.stderr)
    device_id = str(uuid.uuid4())
    session_id = str(uuid.uuid4())
    login_url = get_login_url(device_id, session_id)
    xsrf_name, xsrf_value, tid = get_login_values(login_url)
    code, state = do_login_request(login_url, xsrf_name, xsrf_value, tid, user, password)
    basic_auth = authenticate(device_id, session_id, code, state)
    connect_sid = req.cookies.get('connect.sid', domain='.ssom.magentatv.de')
    auth_data = {'basic_auth': basic_auth, 'device_id': device_id, 'session_id': session_id, 'connect_sid': connect_sid}
    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))
    if auth.get('connect_sid'):
        req.cookies.set('connect.sid', auth['connect_sid'], domain='.ssom.magentatv.de')
    basic_auth = authenticate(auth['device_id'], auth['session_id'])
    return basic_auth, auth['device_id']

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

    if action == "channels":
        output = {'Channels': []}
        for c in get_channels():
            media_pid = c.get('era$mediaPids', {}).get('urn:theplatform:tv:location:any', '')
            output['Channels'].append({'Name': c['title'], 'Mode': "live", 'SessionManifest': True, 'ManifestScript': 'media=' + media_pid, 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'media=' + media_pid, 'Video': 'best'})
        print(json.dumps(output, indent=2))
    elif action == "events":
        output = {'Events': []}
        for c in get_channels():
            media_pid = c.get('era$mediaPids', {}).get('urn:theplatform:tv:location:any', '')
            output['Events'].append({'Name': c['title'], 'Mode': "live", 'SessionManifest': True, 'ManifestScript': 'media=' + media_pid, 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'media=' + media_pid, '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":
        media_id = id.replace('media=', '') if id.startswith('media=') else id
        url, pid, lock, lock_id, lock_token = get_single(basic_auth, media_id)
        unlock(device_id, lock, lock_id, lock_token)
        pssh_data = get_pssh_from_mpd(url)
        output = {"Cdn": [], "ManifestUrl": url, "Headers": {"Manifest": {'User-Agent': user_agent}, "Media": {'User-Agent': user_agent}}, "Pid": pid, "BasicAuth": basic_auth}
        if pssh_data:
            output['Pssh'] = pssh_data
        print(json.dumps(output))
    elif action == "cdm" and cdm_param == "external":
        media_id = id.replace('media=', '') if id.startswith('media=') else id
        url, pid, lock, lock_id, lock_token = get_single(basic_auth, media_id)
        unlock(device_id, lock, lock_id, lock_token)
        pssh_data = get_pssh_from_mpd(url)
        if pssh_data:
            for key in do_cdm_external(pssh_data, basic_auth, pid):
                print(key)

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