#!/usr/bin/python3
import sys
import os
import o11
import json
import datetime
import pytz
import jwt
import m3u8
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 = '/NjpwWorld_' + 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, lic_url):
    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 = {'content-type': 'application/octet-stream', 'origin': 'https://watch.njpwworld.com', 'referer': 'https://watch.njpwworld.com/', 'user-agent': user_agent}
    licence = req.post(lic_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 = {'User-Agent': user_agent}
    response = req.get(url, headers=headers)
    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:
                return pssh_tag.text
    m3u8_obj = m3u8.loads(response.content.decode())
    for playlist in m3u8_obj.playlists:
        p_url = playlist.uri
        url_parts = url.split('/')
        url_parts.pop()
        p_url = '/'.join(url_parts) + '/' + p_url
        response2 = req.get(p_url, headers=headers)
        m3u8_obj2 = m3u8.loads(response2.content.decode())
        for key in m3u8_obj2.keys:
            if key and key.uri:
                return key.uri.split(',')[-1]
    return None

def check_event_status(asset_id):
    headers = {'accept': 'application/json, text/plain, */*', 'content-type': 'application/json', 'origin': 'https://watch.njpwworld.com', 'referer': 'https://watch.njpwworld.com/', 'user-agent': user_agent}
    params = {'device_type': 'web'}
    json_data = {'headers': {'normalizedNames': {}, 'lazyUpdate': [{'name': 'Accept-Language', 'value': 'en', 'op': 'a'}], 'headers': {}, 'lazyInit': {'normalizedNames': {}, 'lazyUpdate': None, 'headers': {}}}, 'params': {'updates': [{'param': 'device_type', 'value': 'web', 'op': 'a'}], 'cloneFrom': {'updates': None, 'cloneFrom': None, 'encoder': {}, 'map': None}, 'encoder': {}, 'map': None}}
    response = req.post(f'https://beacon.playback.api.brightcove.com/tvasahi/api/assets/{asset_id}/live_event_status', params=params, headers=headers, json=json_data)
    data = response.json()
    return data.get('data', {}).get('event_status') == 'available'

def get_events(token):
    headers = {'accept': 'application/json, text/plain, */*', 'authorization': 'Bearer ' + token, 'origin': 'https://watch.njpwworld.com', 'referer': 'https://watch.njpwworld.com/', 'user-agent': user_agent}
    params = {'cohort': '98', 'device_type': 'web', 'device_layout': 'web'}
    response = req.get('https://beacon.playback.api.brightcove.com/tvasahi/api/menus/default/option/13916-home', params=params, headers=headers)
    data = response.json()
    blocks = data.get('data', {}).get('screen', {}).get('blocks', [])
    result = []
    for b in blocks:
        widgets = b.get('widgets', [])
        if widgets and widgets[0].get('name') == 'Live':
            contents = widgets[0].get('playlist', {}).get('contents', [])
            for c in contents:
                asset_id = c.get('id')
                if check_event_status(asset_id):
                    result.append({'name': c.get('name', ''), 'id': asset_id})
    return result

def get_stream_id(token, asset_id):
    decoded = jwt.decode(token, options={"verify_signature": False})
    headers = {'accept': 'application/json, text/plain, */*', 'authorization': 'Bearer ' + token, 'origin': 'https://watch.njpwworld.com', 'referer': 'https://watch.njpwworld.com/', 'user-agent': user_agent}
    params = {'device_type': 'web', 'ngsw-bypass': '1'}
    response = req.get(f'https://beacon.playback.api.brightcove.com/tvasahi/api/account/{decoded["uid"]}/asset_info/{asset_id}', params=params, headers=headers)
    return response.json().get('data', {}).get('video_playback_details', [{}])[0].get('stream_id', '')

def get_video_number_and_policy_key(token, asset_id, stream_id):
    headers = {'accept': 'application/json, text/plain, */*', 'authorization': 'Bearer ' + token, 'origin': 'https://watch.njpwworld.com', 'referer': 'https://watch.njpwworld.com/', 'user-agent': user_agent}
    params = {'cohort': '98', 'device_type': 'web', 'device_layout': 'web'}
    response = req.post(f'https://beacon.playback.api.brightcove.com/tvasahi/api/assets/{asset_id}/streams/{stream_id}', params=params, headers=headers, json={})
    data = response.json().get('data', {}).get('stream', {})
    video_details = data.get('video_provider_details', {})
    return data.get('url', ''), video_details.get('policy_key', ''), video_details.get('account_id', '')

def get_single(policy_key, account_number, video_number):
    headers = {'accept': f'application/json;pk={policy_key}', 'origin': 'https://watch.njpwworld.com', 'referer': 'https://watch.njpwworld.com/', 'user-agent': user_agent}
    response = req.get(f'https://edge.api.brightcove.com/playback/v1/accounts/{account_number}/videos/{video_number}', headers=headers)
    data = response.json()
    for s in data.get('sources', []):
        if 'com.widevine.alpha' in s.get('key_systems', {}):
            return s.get('src', ''), s['key_systems']['com.widevine.alpha'].get('license_url', '')
    return None, None

def do_token_refresh(refresh_tok):
    headers = {'accept': 'application/json, text/plain, */*', 'content-type': 'application/x-www-form-urlencoded', 'origin': 'https://watch.njpwworld.com', 'referer': 'https://watch.njpwworld.com/', 'user-agent': user_agent}
    data = {'grant_type': 'refresh_token', 'scope': 'email openid profile offline_access', 'refresh_token': refresh_tok, 'client_id': 'A1aAWgSZqJaMH8estWD0HncSesxHrAy4'}
    response = req.post('https://auth.njpwworld.com/token', headers=headers, data=data)
    resp_data = response.json()
    return resp_data.get('access_token', ''), resp_data.get('refresh_token', '')

def login():
    print("Please set refresh_token in token file manually", file=sys.stderr)
    auth_data = {'refresh_token': ''}
    json.dump(auth_data, open(os.path.abspath(os.path.dirname(__file__)) + authFile, 'w'))
    print("Token file created - add your refresh_token", file=sys.stderr)

def get_auth():
    auth = json.load(open(os.path.abspath(os.path.dirname(__file__)) + authFile))
    token, new_refresh = do_token_refresh(auth['refresh_token'])
    auth_data = {'refresh_token': new_refresh}
    json.dump(auth_data, open(os.path.abspath(os.path.dirname(__file__)) + authFile, 'w'))
    return token

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

    if action == "channels" or action == "events":
        output = {'Events' if action == "events" else 'Channels': []}
        for c in get_events(token):
            item = {'Name': c['name'].replace('/', '-'), 'Mode': "live", 'SessionManifest': True, 'ManifestScript': 'aid=' + str(c['id']), 'CdmType': "widevine", 'UseCdm': True, 'Cdm': 'aid=' + str(c['id']), 'Video': 'best'}
            if action == "events":
                item['Autostart'] = True
                item['Start'] = int(datetime.datetime.now(pytz.UTC).timestamp())
                item['End'] = int((datetime.datetime.now(pytz.UTC) + datetime.timedelta(hours=4)).timestamp())
            output['Events' if action == "events" else 'Channels'].append(item)
        print(json.dumps(output, indent=2))
    elif action == "manifest":
        asset_id = id.replace('aid=', '') if id.startswith('aid=') else id
        stream_id = get_stream_id(token, asset_id)
        video_number, policy_key, account_number = get_video_number_and_policy_key(token, asset_id, stream_id)
        url, lic_url = get_single(policy_key, account_number, video_number)
        pssh_data = get_pssh_from_mpd(url) if url else None
        output = {"Cdn": [], "ManifestUrl": url, "Headers": {"Manifest": {'User-Agent': user_agent}, "Media": {'User-Agent': user_agent}}, "LicenseUrl": lic_url}
        if pssh_data:
            output['Pssh'] = pssh_data
        print(json.dumps(output))
    elif action == "cdm" and cdm_param == "external":
        asset_id = id.replace('aid=', '') if id.startswith('aid=') else id
        stream_id = get_stream_id(token, asset_id)
        video_number, policy_key, account_number = get_video_number_and_policy_key(token, asset_id, stream_id)
        url, lic_url = get_single(policy_key, account_number, video_number)
        pssh_data = get_pssh_from_mpd(url) if url else None
        if pssh_data and lic_url:
            for key in do_cdm_external(pssh_data, lic_url):
                print(key)

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