#!/usr/bin/python3
import sys
import os
import o11
import json
import datetime
import pytz
import jwt
import time
import base64
import uuid
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 = '/CignalPlay_' + 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())

def do_cdm_external(pssh_data, license_url, bearer_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 = {'Referer': 'https://www.cignalplay.com/', 'authorization': 'Bearer ' + bearer_token, '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 = {'Referer': 'https://www.cignalplay.com/', 'User-Agent': user_agent}
    response = req.get(url, headers=headers)
    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
    return None

def get_channels():
    headers = {'Accept': 'application/json, text/plain, */*', 'Referer': 'https://www.cignalplay.com/', 'User-Agent': user_agent}
    params = {'policy_evaluate': 'false', 'pageNumber': '1', 'pageSize': '25', 'reg': 'ph', 'dt': 'web', 'client': 'pldt-cignal-web'}
    response = req.get('https://catalog-service-cdn.api.pldt.firstlight.ai/catalog/storefront/2DB25A6A-7F9C-4ACE-9DFA-B63E7264BAA9/7C5DC10D-980E-4373-8D2F-A5015FB7834E/containers', params=params, headers=headers)
    data = response.json().get('data', [])
    channels = []
    for d in data:
        for c in d.get('cd', []):
            channels.append({'name': c.get('lon', [{}])[0].get('n', ''), 'content_id': c.get('id', '')})
    return channels

def get_bearer_token():
    headers = {'Referer': 'https://www.cignalplay.com/', 'User-Agent': user_agent, 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'}
    data = {'client_id': 'webclient-ui-app', 'client_secret': '25d5ffc8-37aa-4930-a3ea-20e676ce5529', 'grant_type': 'client_credentials', 'audience': 'edge-service', 'scope': 'offline openid'}
    response = req.post('https://auth.api.cignal.firstlight.ai/oauth2/token', headers=headers, data=data)
    return response.json().get('access_token', '')

def get_access_token(refresh_token):
    headers = {'Accept': 'application/json, text/plain, */*', 'Content-type': 'application/json', 'Referer': 'https://www.cignalplay.com/', 'User-Agent': user_agent}
    data = {'RefreshTokenRequestMessage': {'refreshToken': refresh_token, 'channelPartnerID': 'CIGNAL_PH', 'apiUser': 'cignalevapiuser', 'apiPassword': 'C!gnal@p1u$er@123'}}
    response = req.post('https://rest-prod-cignal.evergent.com/cignal/refreshToken', headers=headers, json=data)
    return response.json().get('RefreshTokenResponseMessage', {}).get('accessToken', '')

def get_x_auth_token(access_token):
    headers = {'Authorization': 'Bearer ' + access_token, 'User-Agent': user_agent, 'Content-Type': 'application/json', 'Accept': 'application/json, text/plain, */*', 'Referer': 'https://www.cignalplay.com/'}
    data = {'GetEntitlementsRequestMessage': {'channelPartnerID': 'CIGNAL_PH', 'apiUser': 'cignalevapiuser', 'apiPassword': 'C!gnal@p1u$er@123', 'returnProductAttributes': True}}
    response = req.post('https://rest-prod-cignal.evergent.com/cignal/getEntitlements', headers=headers, json=data)
    return response.json().get('GetEntitlementsResponseMessage', {}).get('ovatToken', '')

def get_device_id(access_token):
    decoded = jwt.decode(access_token, options={"verify_signature": False})
    return decoded.get('serialNo', '')

def register_stream(bearer_token, x_auth_token, device_id):
    headers = {'Authorization': 'Bearer ' + bearer_token, 'User-Agent': user_agent, 'Content-Type': 'text/plain;charset=UTF-8', 'Referer': 'https://www.cignalplay.com/', 'X-Client-Id': 'pldt-cignal-web', 'X-Authorization': x_auth_token}
    data = {"uniqueId": device_id}
    response = req.post('https://device-register-service.api.cignal.firstlight.ai/device/app/register', headers=headers, json=data)
    resp_data = response.json().get('data', {})
    return resp_data.get('secret', ''), resp_data.get('expiry', 0)

def make_x_device_id(device_id, secret, expiry):
    current_milis = round(time.time(), 3)
    return jwt.encode({"deviceId": device_id, "aud": "playback-auth-service", "iat": current_milis, "exp": current_milis + expiry}, base64.b64decode(secret), algorithm="HS256")

def get_mpd_and_license_url(bearer_token, x_auth_token, x_device_id, device_id, content_id):
    headers = {'Authorization': 'Bearer ' + bearer_token, 'User-Agent': user_agent, 'Content-Type': 'application/json', 'Referer': 'https://www.cignalplay.com/', 'X-Client-Id': 'pldt-cignal-web', 'X-Authorization': x_auth_token, 'X-Device-Id': x_device_id}
    data = {'deviceName': 'web', 'deviceId': device_id, 'contentId': content_id, 'contentTypeId': 'live', 'catalogType': 'channel', 'mediaFormat': 'dash', 'drm': 'widevine', 'delivery': 'streaming', 'disableSsai': 'false', 'playbackMode': 'live', 'deviceManufacturer': 'web', 'deviceModelName': 'web', 'deviceModelNumber': 'chrome', 'deviceOs': user_agent, 'deviceToken': x_device_id}
    response = req.post('https://playback-auth-service.api.cignal.firstlight.ai/media/content/authorize', headers=headers, json=data)
    resp_data = response.json().get('data', {})
    return resp_data.get('contentUrl', ''), resp_data.get('licenseUrl', '')

def do_login_request(username, pwd):
    headers = {'Accept': 'application/json, text/plain, */*', 'Content-type': 'application/json', 'Referer': 'https://www.cignalplay.com/', 'User-Agent': user_agent}
    data = {'GetOAuthAccessTokenv2RequestMessage': {'contactUserName': username, 'contactPassword': pwd, 'deviceMessage': {'deviceName': 'web', 'deviceType': 'web', 'modelNo': 'chrome', 'serialNo': generate_device_id(), 'appType': 'Web'}, 'channelPartnerID': 'CIGNAL_PH', 'apiUser': 'cignalevapiuser', 'apiPassword': 'C!gnal@p1u$er@123'}}
    response = req.post('https://rest-prod-cignal.evergent.com/cignal/getOAuthAccessTokenv2', headers=headers, json=data)
    resp_data = response.json().get('GetOAuthAccessTokenv2ResponseMessage', {})
    return resp_data.get('accessToken', ''), resp_data.get('refreshToken', '')

def login():
    print("logging in...", file=sys.stderr)
    access_token, refresh_token = do_login_request(user.upper(), password)
    auth_data = {'refresh_token': refresh_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))
    access_token = get_access_token(auth['refresh_token'])
    bearer_token = get_bearer_token()
    x_auth_token = get_x_auth_token(access_token)
    device_id = get_device_id(access_token)
    return bearer_token, x_auth_token, device_id

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

    if action == "channels":
        output = {'Channels': []}
        for c in get_channels():
            output['Channels'].append({'Name': c['name'], 'Mode': "live", 'SessionManifest': True, 'ManifestScript': 'cid=' + c['content_id'], 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'cid=' + c['content_id'], 'Video': 'best'})
        print(json.dumps(output, indent=2))
    elif action == "events":
        output = {'Events': []}
        for c in get_channels():
            output['Events'].append({'Name': c['name'], 'Mode': "live", 'SessionManifest': True, 'ManifestScript': 'cid=' + c['content_id'], 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'cid=' + c['content_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":
        content_id = id.replace('cid=', '') if id.startswith('cid=') else id
        secret, expiry = register_stream(bearer_token, x_auth_token, device_id)
        x_device_id = make_x_device_id(device_id, secret, expiry)
        url, license_url = get_mpd_and_license_url(bearer_token, x_auth_token, x_device_id, device_id, content_id)
        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, "BearerToken": bearer_token}
        if pssh_data:
            output['Pssh'] = pssh_data
        print(json.dumps(output))
    elif action == "cdm" and cdm_param == "external":
        content_id = id.replace('cid=', '') if id.startswith('cid=') else id
        secret, expiry = register_stream(bearer_token, x_auth_token, device_id)
        x_device_id = make_x_device_id(device_id, secret, expiry)
        url, license_url = get_mpd_and_license_url(bearer_token, x_auth_token, x_device_id, device_id, content_id)
        pssh_data = get_pssh_from_mpd(url)
        if pssh_data and license_url:
            for key in do_cdm_external(pssh_data, license_url, bearer_token):
                print(key)

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