#!/usr/bin/python3
import sys
import os
import o11
import json
import datetime
import pytz
import base64
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')
sso_param = o11.parse_params(sys.argv, 'sso')
konto_param = o11.parse_params(sys.argv, 'konto')

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 = '/PlayerPL_' + user + '.tokens'
user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36'

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 = {'authority': 'player.pl', 'accept': '*/*', 'origin': 'https://player.pl', 'referer': 'https://player.pl/playerplus/live/', 'user-agent': user_agent, 'content-type': 'application/octet-stream'}
    response = req.get(init_url, headers=headers)
    return to_pssh(response.content)

def do_cdm_external(pssh_data, license_url):
    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 = {'authority': 'player.pl', 'accept': '*/*', 'origin': 'https://player.pl', 'referer': 'https://player.pl/playerplus/live/', 'user-agent': user_agent, 'content-type': 'application/octet-stream'}
    licence = req.post(license_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 = {'authority': 'player.pl', 'accept': '*/*', 'origin': 'https://player.pl', 'referer': 'https://player.pl/playerplus/live/', 'user-agent': user_agent, 'content-type': 'application/octet-stream'}
    response = req.get(url, headers=headers)
    soup = BeautifulSoup(response.content, features="xml")
    init = soup.find('SegmentTemplate')['initialization']
    bandwidth = soup.find('Representation')['bandwidth']
    rep_id = soup.find('Representation')['id']
    base_url = soup.find('BaseURL').text
    init_url = base_url + init.replace('$Bandwidth$', bandwidth).replace('$RepresentationID$', rep_id)
    psshs = init_to_pssh(init_url)
    return psshs[-1] if psshs else None

def get_channels():
    headers = {'accept': 'application/json, text/plain, */*', 'pragma': 'no-cache', 'referer': 'https://player.pl/playerplus/live', 'user-agent': user_agent}
    params = {'4K': 'true', 'platform': 'BROWSER'}
    response = req.get('https://player.pl/playerapi/product/live/list', params=params, headers=headers)
    return response.json()

def get_session_id(konto, channel_id):
    cookies = {'konto_tvn_at': konto}
    headers = {'authority': 'player.pl', 'accept': 'application/json, text/plain, */*', 'referer': 'https://player.pl/playerplus/live/', 'user-agent': user_agent}
    params = {'4K': 'true', 'platform': 'BROWSER', 'type': 'LIVE'}
    response = req.get(f'https://player.pl/playerapi/product/{channel_id}/player/configuration', params=params, cookies=cookies, headers=headers)
    return response.json().get('videoSession', {}).get('videoSessionId', '')

def get_single(konto, channel_id, session_id=None):
    cookies = {'konto_tvn_at': konto}
    headers = {'authority': 'player.pl', 'accept': 'application/json, text/plain, */*', 'referer': 'https://player.pl/playerplus/live/', 'user-agent': user_agent}
    params = {'type': 'LIVE', '4K': 'true', 'platform': 'BROWSER'}
    if session_id:
        params['videoSessionId'] = session_id
    response = req.get(f'https://player.pl/playerapi/item/{channel_id}/playlist', params=params, headers=headers, cookies=cookies)
    data = response.json()
    url = data.get('movie', {}).get('video', {}).get('sources', {}).get('dash', {}).get('url', '')
    if url and '.mpd' not in url:
        url = url.split('?')[0]
    license_url = data.get('movie', {}).get('video', {}).get('protections', {}).get('widevine', {}).get('src', '')
    return url, license_url

def refresh_konto(sso, konto):
    cookies = {'sso_session_is_logged': sso, 'konto_tvn_at': konto}
    headers = {'authority': 'player.pl', 'accept': 'application/json, text/plain, */*', 'referer': 'https://player.pl/playerplus/player-international', 'user-agent': user_agent}
    params = {'4K': 'true', 'platform': 'BROWSER'}
    response = req.get('https://player.pl/playerapi/subscriber/detail', params=params, cookies=cookies, headers=headers)
    new_konto = response.cookies.get('konto_tvn_at', konto)
    return new_konto

def login():
    print("Please set sso and konto tokens in token file manually", file=sys.stderr)
    auth_data = {'sso': sso_param if sso_param else '', 'konto': konto_param if konto_param else ''}
    json.dump(auth_data, open(os.path.abspath(os.path.dirname(__file__)) + authFile, 'w'))
    print("Token file created - add your sso_session_is_logged and konto_tvn_at cookies", file=sys.stderr)

def get_auth():
    auth = json.load(open(os.path.abspath(os.path.dirname(__file__)) + authFile))
    sso = auth['sso']
    konto = refresh_konto(sso, auth['konto'])
    auth['konto'] = konto
    json.dump(auth, open(os.path.abspath(os.path.dirname(__file__)) + authFile, 'w'))
    return sso, konto

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

    if action == "channels":
        output = {'Channels': []}
        for c in get_channels():
            output['Channels'].append({'Name': c['title'], 'Mode': "live", 'SessionManifest': True, 'ManifestScript': 'cid=' + str(c['id']) + '&payable=' + str(c.get('payable', False)), 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'cid=' + str(c['id']) + '&payable=' + str(c.get('payable', False)), 'Video': 'best'})
        print(json.dumps(output, indent=2))
    elif action == "events":
        output = {'Events': []}
        for c in get_channels():
            output['Events'].append({'Name': c['title'], 'Mode': "live", 'SessionManifest': True, 'ManifestScript': 'cid=' + str(c['id']) + '&payable=' + str(c.get('payable', False)), 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'cid=' + str(c['id']) + '&payable=' + str(c.get('payable', False)), '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.replace('cid=', '').split('&payable=')
        channel_id = parts[0]
        payable = parts[1].lower() == 'true' if len(parts) > 1 else False
        session_id = get_session_id(konto, channel_id) if payable else None
        url, license_url = get_single(konto, channel_id, session_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": license_url}
        if pssh_data:
            output['Pssh'] = pssh_data
        print(json.dumps(output))
    elif action == "cdm" and cdm_param == "external":
        parts = id.replace('cid=', '').split('&payable=')
        channel_id = parts[0]
        payable = parts[1].lower() == 'true' if len(parts) > 1 else False
        session_id = get_session_id(konto, channel_id) if payable else None
        url, license_url = get_single(konto, channel_id, session_id)
        if url and license_url:
            pssh_data = get_pssh_from_mpd(url)
            if pssh_data:
                for key in do_cdm_external(pssh_data, license_url):
                    print(key)

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