#!/usr/bin/python3
import sys
import os
import o11
import json
import datetime
import pytz
import base64
import uuid
import secrets
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 = '/Tring_' + user + '.tokens'
user_agent = 'Tring/5.19.65 (Linux;Android 9) ExoPlayerLib/2.14.1'

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 do_cdm_external(pssh_data, license_url, token, cookies):
    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 = {'X-UDRM-Token': token, 'Content-Type': 'application/octet-stream', 'User-Agent': user_agent, 'Accept-Encoding': 'gzip', 'Host': 'fe.tring.al', 'Connection': 'Keep-Alive'}
    licence = req.post(license_url, headers=lic_headers, data=challenge_data, cookies=cookies)
    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)
    location = response.url
    soup = BeautifulSoup(response.content, features="xml")
    psshs = []
    for cp in soup.find_all('ContentProtection'):
        if cp.get('schemeIdUri', '').lower() == 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed':
            pssh_tag = cp.find('cenc:pssh')
            if pssh_tag and pssh_tag.text not in psshs:
                psshs.append(pssh_tag.text)
    if psshs:
        return psshs
    loc_parts = location.rsplit('/', 1)[0]
    for r in soup.find_all('Representation'):
        seg_template = r.find('SegmentTemplate')
        if seg_template:
            init = seg_template.get('initialization')
            if init:
                init_url = loc_parts + '/' + init
                init_resp = req.get(init_url, headers=headers)
                psshs += to_pssh(init_resp.content)
    return list(set(psshs))

def get_channels(cookies, device_id):
    headers = {'x-smartlabs-mac-address': device_id.upper(), 'User-Agent': 'SmartLabs Android app/5.19.65(1201)', 'x-smartlabs-request-id': str(uuid.uuid4()), 'Host': 'fe.tring.al', 'Connection': 'Keep-Alive', 'Accept-Encoding': 'gzip'}
    params = {'lang': 'en'}
    response = req.get('https://fe.tring.al/api/v1/channels/list', params=params, cookies=cookies, headers=headers)
    return response.json().get('result', {}).get('list', [])

def do_login_request(device_id, username, pwd):
    headers = {'x-smartlabs-mac-address': device_id.upper(), 'User-Agent': 'SmartLabs Android app/5.19.65(1201)', 'x-smartlabs-request-id': str(uuid.uuid4()), 'Host': 'fe.tring.al', 'Connection': 'Keep-Alive', 'Accept-Encoding': 'gzip'}
    params = {'login': username, 'password': pwd, 'deviceType': 'ANDROID', 'uuid': device_id, 'terminalName': 'Samsung SM-G955F'}
    req.get('https://fe.tring.al/api/v1/multiroom/link', params=params, headers=headers)

def get_sessid(device_id):
    headers = {'x-smartlabs-mac-address': device_id.upper(), 'User-Agent': 'SmartLabs Android app/5.19.65(1201)', 'x-smartlabs-request-id': str(uuid.uuid4()), 'Host': 'fe.tring.al', 'Connection': 'Keep-Alive', 'Accept-Encoding': 'gzip'}
    params = {'deviceType': 'ANDROID', 'uuid': device_id, 'appVersion': '5.19.65(1201)', 'terminalName': 'Samsung SM-G955F', 'lang': 'en', 'demo': '0'}
    response = req.get('https://fe.tring.al/api/v1/device/authorize', params=params, headers=headers)
    return response.cookies.get('SDPSESSIONID', '')

def login():
    print("logging in...", file=sys.stderr)
    device_id = secrets.token_hex(8)
    do_login_request(device_id, user, password)
    sessid = get_sessid(device_id)
    auth_data = {'SDPSESSIONID': sessid, 'device_id': device_id}
    json.dump(auth_data, open(os.path.abspath(os.path.dirname(__file__)) + authFile, 'w'))
    print("logged in successfully", file=sys.stderr)

def do_action():
    if action == "login":
        login()
        sys.exit()
    try:
        auth = json.load(open(os.path.abspath(os.path.dirname(__file__)) + authFile))
        cookies = {'SDPSESSIONID': auth['SDPSESSIONID']}
        device_id = auth['device_id']
    except:
        return "error"

    if action == "channels":
        output = {'Channels': []}
        for c in get_channels(cookies, device_id):
            output['Channels'].append({'Name': c['name'], 'Mode': "live", 'SessionManifest': True, 'ManifestScript': 'id=' + str(c['id']), 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'id=' + str(c['id']), 'Video': 'best'})
        print(json.dumps(output, indent=2))
    elif action == "events":
        output = {'Events': []}
        for c in get_channels(cookies, device_id):
            output['Events'].append({'Name': c['name'], 'Mode': "live", 'SessionManifest': True, 'ManifestScript': 'id=' + str(c['id']), 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'id=' + 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('id=', '') if id.startswith('id=') else id
        channels = get_channels(cookies, device_id)
        for c in channels:
            if str(c['id']) == channel_id:
                url = c['url']
                if 'https://' not in url:
                    url = url.replace('https:/', 'https://')
                psshs = get_pssh_from_mpd(url)
                lic_token = c.get('drmTokens', [{}])[0].get('token', '')
                lic_url = c.get('drmTokens', [{}])[0].get('licenseServers', [{}])[0].get('url', '')
                output = {"Cdn": [], "ManifestUrl": url, "Headers": {"Manifest": {'User-Agent': user_agent}, "Media": {'User-Agent': user_agent}}, "LicenseUrl": lic_url, "LicenseToken": lic_token}
                if psshs:
                    output['Pssh'] = psshs[0]
                print(json.dumps(output))
                break
    elif action == "cdm" and cdm_param == "external":
        channel_id = id.replace('id=', '') if id.startswith('id=') else id
        channels = get_channels(cookies, device_id)
        for c in channels:
            if str(c['id']) == channel_id:
                url = c['url']
                if 'https://' not in url:
                    url = url.replace('https:/', 'https://')
                psshs = get_pssh_from_mpd(url)
                lic_token = c.get('drmTokens', [{}])[0].get('token', '')
                lic_url = c.get('drmTokens', [{}])[0].get('licenseServers', [{}])[0].get('url', '')
                for p in psshs:
                    for key in do_cdm_external(p, lic_url, lic_token, cookies):
                        print(key)
                break

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