#!/usr/bin/python3
import sys
import os
import o11
import json
import datetime
import pytz
import uuid
from urllib.parse import urlencode, urlparse, urlunparse, parse_qs
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 = '/IrisTV_' + user + '.tokens'
user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'

def generate_device_id():
    return str(uuid.uuid4())[:8] + '-' + str(uuid.uuid4())[9:13] + '-' + str(uuid.uuid4())[14:18] + '-' + str(uuid.uuid4())[19:23] + '-' + str(uuid.uuid4())[24:]

def clean_url(url):
    u = urlparse(url)
    query = parse_qs(u.query, keep_blank_values=True)
    for key in ['zoneoffset', 'servicetype', 'icpid', 'limitflux', 'limitdur', 'it']:
        query.pop(key, None)
    u = u._replace(query=urlencode(query, True))
    return urlunparse(u)

def do_cdm_external(pssh_data, license_url, custom_data):
    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': 'telekomsrbija.live.ott.irdeto.com', 'accept': '*/*', 'acquirelicense.customdata': custom_data, 'content-type': 'application/json; charset=utf-8', 'origin': 'https://iris.mts.rs', 'referer': 'https://iris.mts.rs/', 'user-agent': user_agent}
    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 = {'Accept': '*/*', 'User-Agent': user_agent}
    response = req.get(url, headers=headers)
    for cp in BeautifulSoup(response.content, features="xml").find_all('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(session_cookies):
    headers = {'Accept': '*/*', 'Origin': 'https://iris.mts.rs', 'Referer': 'https://iris.mts.rs/EPG/jsp/webtv/index.html', 'User-Agent': user_agent}
    response = req.post('https://iris.mts.rs/VSP/V3/QueryAllChannel', headers=headers, json={}, cookies=session_cookies)
    return response.json().get('channelDetails', [])

def get_single(session_cookies, channel_id, media_id, business_type):
    headers = {'Accept': '*/*', 'Content-type': 'application/json', 'Origin': 'https://iris.mts.rs', 'Referer': 'https://iris.mts.rs/EPG/jsp/webtv/index.html', 'User-Agent': user_agent}
    json_data = {'channelID': channel_id, 'mediaID': media_id, 'businessType': business_type}
    response = req.post('https://iris.mts.rs/VSP/V3/PlayChannel', headers=headers, json=json_data, cookies=session_cookies)
    data = response.json()
    trigger = data.get('authorizeResult', {}).get('triggers', [{}])[0]
    return data.get('playURL', ''), trigger.get('licenseURL', ''), trigger.get('customData', '')

def get_device_id(session_cookies, username):
    headers = {'Accept': '*/*', 'Content-type': 'application/json', 'Origin': 'https://iris.mts.rs', 'Referer': 'https://iris.mts.rs/EPG/jsp/webtv/index.html', 'User-Agent': user_agent}
    json_data = {'subscriberID': username}
    response = req.post('https://iris.mts.rs/VSP/V3/QueryDeviceList', headers=headers, json=json_data, cookies=session_cookies)
    return response.json().get('devices', [{}])[0].get('physicalDeviceID', '')

def do_login_request(username, pwd, device_id=None):
    if not device_id:
        device_id = generate_device_id()
    headers = {'Accept': '*/*', 'Connection': 'keep-alive', 'Content-type': 'application/json', 'Origin': 'https://iris.mts.rs', 'Referer': 'https://iris.mts.rs/EPG/jsp/webtv/index.html', 'User-Agent': user_agent}
    json_data = {'terminalid': device_id, 'mac': device_id, 'terminaltype': 'Chrome_Irdeto_Widevine', 'terminalvendor': 'OTT', 'osversion': 'Chrome - 120 (Windows - 10)', 'timezone': 'Europe/Belgrade', 'templatename': 'default', 'cnonce': 'e3a27694', 'userName': username, 'subscriberId': username, 'password': pwd, 'softwareVersion': '0.0.77'}
    response = req.post('https://iris.mts.rs/EPG/JSON/TSSSOAuth', headers=headers, json=json_data)
    return response.json().get('retcode', ''), response.cookies.get_dict()

def login():
    print("logging in...", file=sys.stderr)
    retcode, cookies = do_login_request(user, password)
    if retcode != '0':
        device_id = get_device_id(cookies, user)
        retcode, cookies = do_login_request(user, password, device_id)
    auth_data = {'cookies': cookies}
    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))
    retcode, cookies = do_login_request(user, password)
    if retcode != '0':
        device_id = get_device_id(cookies, user)
        retcode, cookies = do_login_request(user, password, device_id)
    return cookies

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

    if action == "channels":
        output = {'Channels': []}
        for c in get_channels(session_cookies):
            media_id = ''
            for pc in c.get('physicalChannels', []):
                if pc.get('fileFormat') == '4':
                    media_id = pc.get('ID', '')
            output['Channels'].append({'Name': c['name'], 'Mode': "live", 'SessionManifest': True, 'ManifestScript': 'ch=' + c['ID'] + '&media=' + media_id, 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'ch=' + c['ID'] + '&media=' + media_id, 'Video': 'best'})
        print(json.dumps(output, indent=2))
    elif action == "events":
        output = {'Events': []}
        for c in get_channels(session_cookies):
            media_id = ''
            for pc in c.get('physicalChannels', []):
                if pc.get('fileFormat') == '4':
                    media_id = pc.get('ID', '')
            output['Events'].append({'Name': c['name'], 'Mode': "live", 'SessionManifest': True, 'ManifestScript': 'ch=' + c['ID'] + '&media=' + media_id, 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'ch=' + c['ID'] + '&media=' + media_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":
        params = dict(p.split('=', 1) for p in id.split('&') if '=' in p)
        channel_id = params.get('ch', '')
        media_id = params.get('media', '')
        url, license_url, custom_data = get_single(session_cookies, channel_id, media_id, 'BTV')
        url = clean_url(url)
        pssh_data = get_pssh_from_mpd(url)
        output = {"Cdn": [], "ManifestUrl": url, "Headers": {"Manifest": {'User-Agent': user_agent}, "Media": {'User-Agent': user_agent}}, "LicenseUrl": license_url, "CustomData": custom_data}
        if pssh_data:
            output['Pssh'] = pssh_data
        print(json.dumps(output))
    elif action == "cdm" and cdm_param == "external":
        params = dict(p.split('=', 1) for p in id.split('&') if '=' in p)
        channel_id = params.get('ch', '')
        media_id = params.get('media', '')
        url, license_url, custom_data = get_single(session_cookies, channel_id, media_id, 'BTV')
        url = clean_url(url)
        pssh_data = get_pssh_from_mpd(url)
        if pssh_data and license_url:
            for key in do_cdm_external(pssh_data, license_url, custom_data):
                print(key)

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