#!/usr/bin/python3
import sys
import os
import o11
import json
import datetime
import pytz
from datetime import timezone
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 = '/A1XploreBG_' + user + '.tokens'
user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36'

def do_cdm_external(pssh_data, device_id):
    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': '*/*', 'Origin': 'https://new.a1xploretv.bg', 'Referer': 'https://new.a1xploretv.bg/', 'User-Agent': user_agent, 'Content-Type': 'application/x-www-form-urlencoded'}
    licence = req.post('https://wvps.a1xploretv.bg:8063/?deviceId=' + device_id, 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)
    location = response.url
    for cp in BeautifulSoup(response.content, features="xml").findAll('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, location
    return None, location

def get_channels(cookies, profile_id):
    headers = {'Origin': 'https://new.a1xploretv.bg', 'Referer': 'https://new.a1xploretv.bg/', 'User-Agent': user_agent, 'Zappware-User-Agent': 'windows_pc_chrome/v28.0.1 (Nexx 4.0 windows_pc_chrome; Windows; 10) null', 'accept': '*/*', 'content-type': 'application/json'}
    current_time_utc = datetime.datetime.utcnow()
    formatted_time = current_time_utc.strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z'
    json_data = {'operationName': 'liveTV', 'variables': {'profileId': profile_id, 'channelListId': '59-6', 'channelAfterCursor': None, 'firstChannels': 300, 'currentTime': formatted_time, 'logoWidth': 76, 'logoHeight': 28, 'thumbnailHeight': 280, 'backgroundHeight': 780, 'backgroundWidth': 1920}, 'query': 'query liveTV($profileId: ID!, $channelAfterCursor: String, $firstChannels: Int!, $currentTime: Date!, $logoWidth: Int!, $logoHeight: Int!, $logoFlavour: ImageFlavour, $thumbnailHeight: Int!, $backgroundHeight: Int!, $backgroundWidth: Int!, $channelListId: ID!) { channelList(id: $channelListId) { name channels(first: $firstChannels, after: $channelAfterCursor) { edges { node { id title } } } } }'}
    response = req.post('https://web.a1xploretv.bg:8443/sdsmiddleware/Mtel/graphql/4.0', cookies=cookies, headers=headers, json=json_data)
    data = response.json()
    channels = []
    for e in data.get('data', {}).get('channelList', {}).get('channels', {}).get('edges', []):
        channels.append(e['node'])
    return channels

def get_mpd(cookies, channel_id, profile_id):
    headers = {'Origin': 'https://new.a1xploretv.bg', 'Referer': 'https://new.a1xploretv.bg/', 'User-Agent': user_agent, 'Zappware-User-Agent': 'windows_pc_chrome/v28.0.1 (Nexx 4.0 windows_pc_chrome; Windows; 10) null', 'accept': '*/*', 'content-type': 'application/json'}
    json_data = {'operationName': 'playChannel', 'variables': {'input': {'channelId': channel_id, 'replaceSessionId': None}, 'profileId': profile_id}, 'query': 'mutation playChannel($input: PlayChannelInput!, $profileId: ID!) { playChannel(input: $input) { playbackInfo { sessionId url } } }'}
    response = req.post('https://web.a1xploretv.bg:8443/sdsmiddleware/Mtel/graphql/4.0', cookies=cookies, headers=headers, json=json_data)
    data = response.json().get('data', {})
    playback_info = data.get('playChannel', {}).get('playbackInfo', {})
    return playback_info.get('url', ''), playback_info.get('sessionId', '')

def stop_playback(cookies):
    headers = {'Origin': 'https://new.a1xploretv.bg', 'Referer': 'https://new.a1xploretv.bg/', 'User-Agent': user_agent, 'Zappware-User-Agent': 'windows_pc_chrome/v28.0.1 (Nexx 4.0 windows_pc_chrome; Windows; 10) null', 'accept': '*/*', 'content-type': 'application/json'}
    json_data = {'operationName': 'stopPlayback', 'variables': {'input': {'sessionId': ''}}, 'query': 'mutation stopPlayback($input: StopPlaybackInput!) { stopPlayback(input: $input) { success } }'}
    req.post('https://web.a1xploretv.bg:8443/sdsmiddleware/Mtel/graphql/4.0', cookies=cookies, headers=headers, json=json_data)

def do_login_request(username, pwd, device_id=''):
    headers = {'User-Agent': user_agent, 'Content-Type': 'application/json', 'Accept': '*/*', 'Origin': 'https://new.a1xploretv.bg', 'Referer': 'https://new.a1xploretv.bg/'}
    params = {'devId': device_id, 'user': username, 'pwd': pwd, 'rqT': 'true'}
    if device_id:
        params['refr'] = 'true'
    response = req.post('https://web.a1xploretv.bg:8843/ext_dev_facade/auth/Login', headers=headers, params=params)
    return response.json().get('token', '')

def check_token(token, device_id):
    headers = {'User-Agent': user_agent, 'Content-Type': 'application/json', 'Accept': '*/*', 'Origin': 'https://new.a1xploretv.bg', 'Referer': 'https://new.a1xploretv.bg/'}
    params = {'devId': device_id, 'token': token, 'apply': 'true'}
    response = req.get('https://web.a1xploretv.bg:8843/ext_dev_facade/auth/CheckToken', headers=headers, params=params)
    return response.json().get('status') == 'OK'

def get_bearer_cookies(token, device_id):
    cookies = {'webfw-1-auth': token}
    headers = {'Zappware-User-Agent': 'windows_pc_chrome/v28.0.1 (Nexx 4.0 windows_pc_chrome; Windows; 10) null', 'User-Agent': user_agent, 'content-type': 'application/json', 'accept': '*/*', 'Origin': 'https://new.a1xploretv.bg', 'Referer': 'https://new.a1xploretv.bg/', 'SDSEVO_DEVICE_ID': device_id, 'SDSEVO_SESSION_ID': '[sedt=0]' + token}
    data = '{"operationName":"keepAlive","variables":{},"query":"mutation keepAlive { keepSessionAlive { sessionTimeout } }"}'
    response = req.post('https://web.a1xploretv.bg:8443/sdsmiddleware/Mtel/graphql/4.0', headers=headers, data=data, cookies=cookies)
    return {'BearerToken': response.cookies.get('BearerToken', '')}

def get_device_id(token):
    cookies = {'webfw-1-auth': token}
    headers = {'Origin': 'https://new.a1xploretv.bg', 'Referer': 'https://new.a1xploretv.bg/', 'SDSEVO_DEVICE_ID': 'none', 'SDSEVO_SESSION_ID': '[sedt=1]' + token, 'User-Agent': user_agent, 'Zappware-User-Agent': 'windows_pc_chrome/v28.0.1 (Nexx 4.0 windows_pc_chrome; Windows; 10) null', 'accept': '*/*', 'content-type': 'application/json'}
    json_data = {'operationName': 'limitedSetup', 'variables': {}, 'query': 'query limitedSetup { me { household { devices { items { id } } } } }'}
    response = req.post('https://web.a1xploretv.bg:8443/sdsmiddleware/Mtel/graphql/4.0', cookies=cookies, headers=headers, json=json_data)
    return response.json().get('data', {}).get('me', {}).get('household', {}).get('devices', {}).get('items', [{}])[0].get('id', '')

def get_profile_id(cookies):
    headers = {'Origin': 'https://new.a1xploretv.bg', 'Referer': 'https://new.a1xploretv.bg/', 'User-Agent': user_agent, 'Zappware-User-Agent': 'windows_pc_chrome/v30.0.2 (Nexx 4.0 windows_pc_chrome; Windows; 10) null', 'accept': '*/*', 'content-type': 'application/json'}
    json_data = {'operationName': 'getSetupSteps', 'variables': {'skipChannels': True}, 'query': 'query getSetupSteps($skipChannels: Boolean!) { me { household { profiles { items { id } } } } }'}
    response = req.post('https://web.a1xploretv.bg:8443/sdsmiddleware/Mtel/graphql/4.0', cookies=cookies, headers=headers, json=json_data)
    return response.json().get('data', {}).get('me', {}).get('household', {}).get('profiles', {}).get('items', [{}])[0].get('id', '')

def login():
    print("logging in...", file=sys.stderr)
    token = do_login_request(user, password)
    device_id = get_device_id(token)
    token = do_login_request(user, password, device_id)
    auth_data = {'token': token, '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 get_auth():
    auth = json.load(open(os.path.abspath(os.path.dirname(__file__)) + authFile))
    token = auth['token']
    device_id = auth['device_id']
    if not check_token(token, device_id):
        raise Exception("Token expired")
    return token, device_id

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

    cookies = get_bearer_cookies(token, device_id)
    profile_id = get_profile_id(cookies)

    if action == "channels":
        output = {'Channels': []}
        for c in get_channels(cookies, profile_id):
            output['Channels'].append({'Name': c['title'], 'Mode': "live", 'SessionManifest': True, 'ManifestScript': 'id=' + c['id'], 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'id=' + c['id'], 'Video': 'best'})
        print(json.dumps(output, indent=2))
    elif action == "events":
        output = {'Events': []}
        for c in get_channels(cookies, profile_id):
            output['Events'].append({'Name': c['title'], 'Mode': "live", 'SessionManifest': True, 'ManifestScript': 'id=' + c['id'], 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'id=' + 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
        url, session_id = get_mpd(cookies, channel_id, profile_id)
        stop_playback(cookies)
        pssh_data, loc_url = get_pssh_from_mpd(url)
        output = {"Cdn": [], "ManifestUrl": loc_url, "Headers": {"Manifest": {'User-Agent': user_agent}, "Media": {'User-Agent': user_agent}}, "DeviceId": device_id}
        if pssh_data:
            output['Pssh'] = pssh_data
        print(json.dumps(output))
    elif action == "cdm" and cdm_param == "external":
        channel_id = id.replace('id=', '') if id.startswith('id=') else id
        url, session_id = get_mpd(cookies, channel_id, profile_id)
        stop_playback(cookies)
        pssh_data, loc_url = get_pssh_from_mpd(url)
        if pssh_data:
            for key in do_cdm_external(pssh_data, device_id):
                print(key)

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