#!/usr/bin/python3
import sys
import os
import o11
import json
import datetime
import pytz
import time
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')

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 = '/TwistTV_' + 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 do_cdm_external(pssh_data, default_kid, license_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': '*/*', 'Content-Type': 'text/plain;charset=UTF-8', 'Origin': 'https://play.twist-tv.com', 'Referer': 'https://play.twist-tv.com/', 'User-Agent': user_agent}
    data = {"token": license_token, "drm_info": list(challenge_data), "kid": default_kid}
    licence = req.post('https://widevine-proxy.drm.technology/proxy', headers=lic_headers, json=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)
    pssh_data, default_kid = None, None
    for cp in BeautifulSoup(response.content, features="xml").findAll('ContentProtection'):
        if cp.get('schemeIdUri') == 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed':
            pssh_tag = cp.find('cenc:pssh')
            if pssh_tag:
                pssh_data = pssh_tag.text
        if cp.get('schemeIdUri') == 'urn:mpeg:dash:mp4protection:2011':
            default_kid = cp.get('cenc:default_KID', '')
    return pssh_data, default_kid

def get_channels():
    headers = {'User-Agent': user_agent, 'Accept': 'application/json, text/plain, */*', 'Accept-Encoding': 'gzip, deflate, br', 'Referer': 'https://twist-tv.com/', 'Origin': 'https://twist-tv.com'}
    current_time = int(round(time.time()))
    response = req.get(f'https://ev-app-api.aws.playco.com/api/media/channel/events?channels=all&ts_start={current_time}&ts_end={current_time+100000}&lang=en&pg=18&page=1&limit=999', headers=headers)
    return response.json().get('data', [])

def get_single(channel_id):
    headers = {'client-type': 'website', 'origin': 'https://play.twist-tv.com', 'referer': 'https://play.twist-tv.com/', 'user-agent': user_agent, 'xtype': 'portal'}
    response = req.get(f'https://ev-api.aws.playco.com/api/v1.0/mediaCatalog/titles/movies/{channel_id}/?lang=en&mediaAssetTypes=dash_widevine_spa,filmstrip_199x110', headers=headers)
    data = response.json()
    for title in data.get('titles', []):
        for media in title.get('media', []):
            for c in media.get('content', []):
                if c.get('protectionScheme') == 'widevine':
                    return c.get('streamingUrl', ''), c.get('releases', [{}])[0].get('pid', '')
    return '', ''

def get_license_token(token, pid):
    headers = {'accept': '*/*', 'authorization': 'Bearer ' + token, 'content-type': 'application/json; charset=UTF-8', 'origin': 'https://play.twist-tv.com', 'referer': 'https://play.twist-tv.com/', 'user-agent': user_agent}
    decoded = json.loads(base64.b64decode(token.split('.')[1] + '==').decode())
    params = {'globalUserId': decoded.get('iss', '')}
    json_data = {'releasePids': [pid], 'protectionScheme': 'widevine'}
    response = req.post('https://ev-api.aws.playco.com/api/v0.2/externalAuthorization/drmToken/vualto', params=params, headers=headers, json=json_data)
    return response.json().get('token', '')

def do_login_request(username, pwd):
    headers = {'User-Agent': user_agent, 'Accept': 'application/json, text/plain, */*', 'Accept-Encoding': 'gzip, deflate, br', 'Referer': 'https://twist-tv.com/', 'Content-Type': 'application/json;charset=utf-8', 'Client-Type': 'website', 'Origin': 'https://twist-tv.com'}
    data = {"username": username, "password": pwd, "tenant": "twist", "cookie": True}
    response = req.post('https://ev-app-api.aws.playco.com/api/auth/v2/login', headers=headers, json=data)
    return response.json().get('spx', '')

def login():
    print("logging in...", file=sys.stderr)
    token = do_login_request(user, password)
    auth_data = {'token': 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))
    return auth['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():
            output['Channels'].append({'Name': c['title'], 'Mode': "live", 'SessionManifest': True, 'ManifestScript': 'cid=' + str(c['id']), 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'cid=' + str(c['id']), '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']), 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'cid=' + str(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, pid = get_single(channel_id)
        pssh_data, default_kid = get_pssh_from_mpd(url) if url else (None, None)
        license_token = get_license_token(token, pid) if pid else ''
        output = {"Cdn": [], "ManifestUrl": url, "Headers": {"Manifest": {'User-Agent': user_agent}, "Media": {'User-Agent': user_agent}}, "Pid": pid, "LicenseToken": license_token, "DefaultKid": default_kid}
        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, pid = get_single(channel_id)
        if url and pid:
            pssh_data, default_kid = get_pssh_from_mpd(url)
            license_token = get_license_token(token, pid)
            if pssh_data and license_token and default_kid:
                for key in do_cdm_external(pssh_data, default_kid, license_token):
                    print(key)

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